Singletons are evil, and here's why.
Fair enough. Yet they exist, and they're not intrinsically evil--just misused. Can we mock enough to make testing them feasible? Yep, and here's a Contrived Example™ that shows how (and why we might want to).
"Embedded" singletons or utility classes can make testing is problematic. Injected singletons are different; then it's an issue of whether or not it should be a singleton at all--different discussion.
"Embedded" singletons look like this. (Utility classes are essentially the same, minus an instance.)
Let's say (a) we need to test this code, (b) we cannot modify this code, and (c) the singleton's constructor is long-running, but required for fetch() execution. For testing, then, we need to (a) avoid the constructor for speed reasons, and (b) mock the fetch() method to return known data for the test. We can't just mock fetch(), because the constructor would still run.
Here's our (contrived) singleton class; we sleep() in the constructor to pretend it's doing something interesting like caching data from a web service, to be returned by fetch().
The undecorated test does what you'd expect, and takes as much time as you'd expect.
Now, with a combination of PowerMock and EasyMock, we'll put the kibosh on that, eliminate the constructor, and return the data we want. (PowerMock sits on top of EasyMock or Mockito. (Both are great, although I tend towards Mockito.)
Most of the test class is self-explanatory. The nutshell version is that we annotate the test class itself and tell it to run with the PowerMock runner (@RunWith), and that we're going to be messing with LongRunningCtor's innards (@PrepareForTest).
Inside the test itself, we tell it to suppress LongRunningCtor's constructor (before mocking, otherwise the constructor will fire during the createPartialMock() call). We also prepare for mayhem by calling mockStatic(). (This mocks all the class's static methods; we could also choose specific static methods to mock using mockStaticPartial().)
Our test now takes a fraction of the time because we're skipping the slow constructor, and our mock returns known data so we can exercise only the calling code.
Ideally, code is structured so this kind of byte-code treachery is unnecessary--it's a great reason for dependency injection/inversion of control. In the real world, technical and timing constraints don't always allow the kind of restructuring we'd like.
With the aid of some tools that do the low-level dirty work for us, we have a relatively clean way to work around some types of system limitations, and still write tests that can execute quickly.
The gist used in this post also includes the relevant Maven dependencies.
Saturday, November 12, 2011
Thursday, November 10, 2011
A trip to the Rake-track.
(Because it's like "racetrack", and it makes Rake tasks faster, and... oh, never mind.)
Tired of waiting for Ruby to spin up just so you can run a "routes" command, or your latest "db:migrate"?
Use rake-sh and start up a rake console for running tasks without the initial spin up. It'll take a "rake routes" from four seconds down to under a second. Rake task completion? Naturally. Use "t" for tasks, "! " (note there's a space after the bang) to run a shell command.
If you get a message about an "uninitialized constant Rake::DSL" when you run rake-sh, no sweat: add require 'rake/dsl_definition' to the top of your Rakefile.
Breaking flow sucks for developers: a few seconds doesn't sound like much. In actual time, it isn't much--but it's the distractions that kill, the times-between-doing. Keep them to a minimum by fully exploiting your tools.
Tired of waiting for Ruby to spin up just so you can run a "routes" command, or your latest "db:migrate"?
Use rake-sh and start up a rake console for running tasks without the initial spin up. It'll take a "rake routes" from four seconds down to under a second. Rake task completion? Naturally. Use "t" for tasks, "! " (note there's a space after the bang) to run a shell command.
If you get a message about an "uninitialized constant Rake::DSL" when you run rake-sh, no sweat: add require 'rake/dsl_definition' to the top of your Rakefile.
Breaking flow sucks for developers: a few seconds doesn't sound like much. In actual time, it isn't much--but it's the distractions that kill, the times-between-doing. Keep them to a minimum by fully exploiting your tools.
Thursday, September 15, 2011
Rails 3 Custom Validator Quandary -- Solution Step One
Remember when I had a Rails 3 custom validator quandary? My bottom-line question was "how should I access a specific error condition, cleanly, in both an action, and a template?" I sketched a few solutions, ranging from checking for a specific error message to providing a function that indicates if the error has occurred.
For now, I want simple boolean methods on the model to encapsulate the error condition. I don't want to write them by hand; I want the validator itself to dictate the interface. For example, if I'm validating an "email" property that can only repeat n times on a given day, I'd like to have an email_repeats_error? method available in the model, returning what we'd expect.
This will be developed in two steps: first, as a simple library (makes tweaking easier, at least for me). Second, as a standalone gem (the way it should be). Naturally, this led to a brief WTF spike as I couldn't figure out how to get the library to expose the validator properly.
Loading the Library
Rails 3.0 changed how libraries are loaded, at least a little bit. I did two things to get things loading from an arbitrarily-named file in the /lib directory. First in my application.rb file, I added the /lib directory to the autoload path.
module RepeatsValidatorWork
class Application < Rails::Application
config.autoload_paths += %W(#{config.root}/lib)
# auto-require; see next code fragment.
pry(main)> i = Item.new(:name => 'wat!')
=> #
pry(main)> i.name_repeats_error?
=> nil
Huh? Oh, right--the model hasn't been validated yet. Remember, as it stands right now, the method is added during validation.
(This may be a Bad Idea--it might be better to add the method to the model in some other way, but unless we add it manually, or add it to all our models, create a method our models can use to get the method from the same module our validator is in, or...? this (for now!) seems the way most-tied-to-the-validator, which was one of the things I wanted.)
We'll use create instead for illustration and keep creating items with the same name. Once we have two with the same name, additional items with the same name should have a validation error.
pry(main)> i = Item.create(:name => 'wat!')
=> #<Item id: 7, name: "wat!", ...>
pry(main)> i.name_repeats_error?
=> false
pry(main)> i = Item.create(:name => 'wat!')
=> #<Item id: 8, name: "wat!", ...>
pry(main)> i.name_repeats_error?
=> false
pry(main)> i = Item.create(:name => 'wat!')
=> #<Item id: nil, name: "wat!", ...>
pry(main)> i.name_repeats_error?
=> true
That, as they say, is that.
For now.
For now, I want simple boolean methods on the model to encapsulate the error condition. I don't want to write them by hand; I want the validator itself to dictate the interface. For example, if I'm validating an "email" property that can only repeat n times on a given day, I'd like to have an email_repeats_error? method available in the model, returning what we'd expect.
This will be developed in two steps: first, as a simple library (makes tweaking easier, at least for me). Second, as a standalone gem (the way it should be). Naturally, this led to a brief WTF spike as I couldn't figure out how to get the library to expose the validator properly.
Loading the Library
Rails 3.0 changed how libraries are loaded, at least a little bit. I did two things to get things loading from an arbitrarily-named file in the /lib directory. First in my application.rb file, I added the /lib directory to the autoload path.
module RepeatsValidatorWork
class Application < Rails::Application
config.autoload_paths += %W(#{config.root}/lib)
# auto-require; see next code fragment.
# ... etc ...
Second, I require the library manually since... well, I'm not 100% sure I understand why, although this blog entry pointed me in the right direction. For testing purposes, this is fine--it might not be a great idea in general. Once it's in a gem, it won't matter anyway.
config.autoload_paths.each do |path|
Dir["#{path}/*.rb"].each do |file|
puts "Auto-requiring #{file}..."
require file
end
end
The Validator Proper
We'll work a bit backwards to get to the final implementation--starting with the familiar and common, ending with tasty snacks.
I'd like to configure the validator as below. For now, we'll just use a lambda to determine how to count the repeats--in the future we might want shortcuts for common use cases. All mine have been different so far, so a lambda it is. We'll pass it the same things a custom validator normally gets (see next code block).
class Item < ActiveRecord::Base
validates :name,
:presence => true,
:repeats => {
:upto => 2,
:how_count => lambda { |rec, attr, val|
Item.count(:conditions => ['name=?', val])
}
}
end
The validator is an ActiveModel::EachValidator subclass. We'll save the "upto" and "how_count" options in the class's initialize method. Since we'll care which model attributes are using the validator, we'll save that before calling super: Rails initializes that option, and plucks it out again in the base class. If we want 'em, we gotta act fast.
The validate_each method uses a saved copy of the "upto" option, runs the "how_count" lambda, and compares the values.
def initialize(options)
@attributes = options[:attributes]
super
@how_count = options[:how_count]
@upto = options[:upto]
end
def validate_each(record, attr, value)
err = false
if @how_count.call(record, attr, value) >= @upto
record.errors[attr] << (options[:message] \
|| "repeats too much")
err = true
end
record.instance_variable_set \
"@#{attr}_repeats_error", err
end
The last line of the method is important: it sets an instance property to true or false depending on whether or not we had a repeats violation. Now we just need to access that in an attribute-specific method for every attribute that uses this validator.
The final bit of pseudo-magic occurs in our class's setup method. Turns out that Rails validation internals call a setup method in each of the registered validators that defines one. (Discovered while looking through Rails source--I can't emphasize the usefulness of reading through source code.) The argument is the class of the model--if that sounds handy, you're on to something.
The final bit of pseudo-magic occurs in our class's setup method. Turns out that Rails validation internals call a setup method in each of the registered validators that defines one. (Discovered while looking through Rails source--I can't emphasize the usefulness of reading through source code.) The argument is the class of the model--if that sounds handy, you're on to something.
def setup(clazz)
clazz.class_eval @attributes.inject("") {|s, attr| s += <<END
def #{attr}_repeats_error?
@#{attr}_repeats_error
end
END
end
This code is less-dense than it seems (it seems dense to me, I guess). In a nutshell, we're looping over our saved attributes (in @attributes) and using inject to build up a string containing everything between the two upper-case ENDs (except for the closing bracket). (Didn't know that was legal syntax.) If it makes it easier to visualize, think of it like this:
def plain_ol_setup(clazz)
s = ""
@attributes.each do |attr|
s += """
def #{attr}_repeats_error?
@#{attr}_repeats_error
end
"""
end
clazz.class_eval s
end
If it didn't make sense before, does it now? We're adding a method
The only thing left to do is to tell Rails that our validator is alive and we wants me some; this is just a single line at the bottom of our library file. (The validator code above lives in some nested modules.)
ActiveModel::Validations.__send__(:include, Newton::Validator)
Here's the complete code in a gist (also appended at the bottom of the post).
So, does it work?
pry(main)> i = Item.new(:name => 'wat!')
=> #
pry(main)> i.name_repeats_error?
=> nil
Huh? Oh, right--the model hasn't been validated yet. Remember, as it stands right now, the method is added during validation.
(This may be a Bad Idea--it might be better to add the method to the model in some other way, but unless we add it manually, or add it to all our models, create a method our models can use to get the method from the same module our validator is in, or...? this (for now!) seems the way most-tied-to-the-validator, which was one of the things I wanted.)
We'll use create instead for illustration and keep creating items with the same name. Once we have two with the same name, additional items with the same name should have a validation error.
pry(main)> i = Item.create(:name => 'wat!')
=> #<Item id: 7, name: "wat!", ...>
pry(main)> i.name_repeats_error?
=> false
pry(main)> i = Item.create(:name => 'wat!')
=> #<Item id: 8, name: "wat!", ...>
pry(main)> i.name_repeats_error?
=> false
pry(main)> i = Item.create(:name => 'wat!')
=> #<Item id: nil, name: "wat!", ...>
pry(main)> i.name_repeats_error?
=> true
That, as they say, is that.
For now.
Tuesday, September 06, 2011
Simple Ajax property toggle in Rails 3.0
Entry-level overview of one way to add trivial Ajax functionality to a Rails 3.0 app, originally written for a specific audience. The repository is on github.
Original Model Index View
Update the Template
Let's say we have an Article model with an "approved" flag. We need to be able to toggle this flag. Normal scaffolding would have us view the article, click a checkbox, and submit. We'll keep that functionality, but add a simple Ajax-based toggle on the articles index page.
Model Generation
rails g scaffold Article name:string approved:boolean
Original Model Index View
The default scaffolding includes the following chunk, repeated for each article.
<td><%= article.name %></td> <td><%= article.approved %></td> ...
An Approving Helper
"True" and "false" are a yucky user experience, what we really want is "approved" and "un-approved". We'll think ahead, and turn it instead into text suitable for a command link. We could also use images, but we'll stick with text for now.
def approve_link_text(approvable) approvable.approved? ? 'Un-approve' : 'Approve' end
Two quick things about how this was written.
1) It's not tied to articles; it'll quack at anything with an "approved?" method.
2) The parameter is named something that provides a hint to future devs, more useful in an IDE that provides popup help.
Add a Method to the ArticlesController to Toggle Approval Status
Two-step process: first, add a method to the articles resources routing.
resources :articles do get 'toggle_approve', :on => :member end
Then the method itself (this is not the final version).
def toggle_approve @a = Article.find(params[:id]) @a.toggle!(:approved) render :nothing => true end
Update the Template
Our first step will just toggle the attribute without any feedback. It's actually a one-liner, just make the true/false "approved" text from before be a link to our new action.
<%= link_to approve_link_text(article), toggle_approve_article_path(article), :remote => true %>
The :remote => true turns it into an Ajax request. Not too shabby.
If we refresh the page after clicking the link we'll see that the text has changed. Approved articles will have an "Un-approve" link, un-approved articles an "Approve" link. Clicky-clicky, it does what we expect.
But refreshing is deeply unsatisfying.
Dynamic Feedback, Part 1
Remember when fadey-yellow things were cool? Yeah, we're all about that.
In order to change stuff on our page, we have to be able to access the appropriate DOM elements. Let's say we'd like to highlight the entire row when we toggle an article's approval status, and change the link text.
We'll use "article_n" for the article rows, and "approval_link_n" for the links.
<% @articles.each do |article| %> <%= article.name %> <%= link_to approve_link_text(@article), toggle_approve_article_path(@article), :remote => true, :id => "approve_link_#{@article.id}" %>
Dynamic Feedback, Part 2
Now we can access the DOM elements we care about on a per-article basis. How do we make them change? By creating a JavaScript template for our action, just like we usually create HTML templates. Rails will execute the returned JavaScript. The JavaScript template can contain ERb constructs, also just like HTML templates.
Note that some people don't like creating JavaScript this way. An alternative is to create DOM elements that our JavaScript can pull from. That's fine, but honestly, for simple things like this, I don't have a big problem with doing it the "old-fashioned way".
Our action is called "toggle_approve". We name JavaScript templates the same way we do ERb templates, so we'll create a toggle_approve.js.erb.
$("#approve_link_<%= @article.id %>").text("<%= approve_link_text(@article) %>"); $("#article_<%= @article.id %>").effect("highlight");
Line 1 sets the link text to Approve/Un-approve using the same helper we used in the HTML template.
Line 2 highlights the row we just updated.
That's it--we no longer have to refresh our page--the text of our link changes after the property is toggled, the row is highlighted, and we're all set!
Monday, September 05, 2011
Example app test failures from authlogic user sessions
This question on stackoverflow led me to believe that if I actually ran rake test that my example app's tests would fail--and they did, with the same error. (Why I wasn't running tests from the beginning? Meh!) The tests throw up wads of stack trace, headed with this:
SQLException: no such table: user_sessions: DELETE FROM "user_sessions" WHERE 1=1
What's causing this error? We created our user session object like this:
rails g authlogic:session UserSession
Unfortunately, this created a fixture file as well. During testing, fixture files normally map to tables, from which all data is deleted before subsequent reloading of the fixture.
But authlogic user sessions aren't ActiveRecord models, they're AR-like, and are actually authlogic-derived classes. No table: they serve as a link between users (which are in a table) and their authlogic sessions. My takeaway? Authlogic sessions should be created without a fixture file.
rails g authlogic:session UserSession --skip-fixture # Or --fixture=false
SQLException: no such table: user_sessions: DELETE FROM "user_sessions" WHERE 1=1
What's causing this error? We created our user session object like this:
rails g authlogic:session UserSession
Unfortunately, this created a fixture file as well. During testing, fixture files normally map to tables, from which all data is deleted before subsequent reloading of the fixture.
But authlogic user sessions aren't ActiveRecord models, they're AR-like, and are actually authlogic-derived classes. No table: they serve as a link between users (which are in a table) and their authlogic sessions. My takeaway? Authlogic sessions should be created without a fixture file.
rails g authlogic:session UserSession --skip-fixture # Or --fixture=false
The file has been removed from the example app's github repository.
Two other minor changes were made to get the tests to pass (w/o any care to what they're testing). First, the user fixture file was tweaked (replace the old "login" property with the new "nickname" property, make the persistence tokens unique). Second, the HomeControllerTest stub was tweaked to expect a 302 when hitting the index page, which is the redirect encountered when the user isn't logged in.
TextMate, rvm, ActiveSupport outside of Rails, "require"s from current directory, all together now!
I'm prototyping some calendar/date stuff for a Rails app in standalone scripts, and want access to both normal Rails things (in this case, ActiveSupport's date math, like Date.today - 3.days) and my own classes within the prototyping directory. I'd like to continue using TextMate's "Run" command to run the current buffer as a Ruby script, since it's convenient. I'm using rvm; my prototyping directory has the same .rvmrc file as my Rails app.
My previous TextMate/rvm post (written for a different MacBook Pro) detailed setting TM's PATH environment variable; on this one I didn't have to do that--I just set TM's TM_RUBY to rvm's auto-ruby, ~/.rvm/bin/rvm-auto-ruby, and with a simple require 'active_support/all', date math.
Setting TM_RUBY made my own requires of code in the prototype directory fail, though. My quick fix was to set TM's RUBYLIB env var to .:${RUBYLIB} since everything I needed for this was in the current directory.
Potentially a little gross, but for now, it's letting me continue. I suspect I may rely more and more on Pry for this type of hacking in the future.
My previous TextMate/rvm post (written for a different MacBook Pro) detailed setting TM's PATH environment variable; on this one I didn't have to do that--I just set TM's TM_RUBY to rvm's auto-ruby, ~/.rvm/bin/rvm-auto-ruby, and with a simple require 'active_support/all', date math.
Setting TM_RUBY made my own requires of code in the prototype directory fail, though. My quick fix was to set TM's RUBYLIB env var to .:${RUBYLIB} since everything I needed for this was in the current directory.
Potentially a little gross, but for now, it's letting me continue. I suspect I may rely more and more on Pry for this type of hacking in the future.
Using email or nickname to log in using authlogic and Rails 3
This is a continuation of my first authlogic/Rails 3 post.
Authlogic users have both "login" and "email" properties by default. I wanted to allow users to log in via their email address or nickname. I tackled this in two steps. First, switch to logging in using the "email" property. Second, create a "nickname" property, and allow users to log in with either one. (Yes, I actually wanted to call it "nickname"; I probably could have just aliased it or something.)
In addition to the column-dropping migration, I needed to change the authlogic config to use the "email" property for user lookup, and to not validate the "login" property, since it no longer existed. This is handled in the acts_as_authentic configuration as discussed previously.
Authlogic users have both "login" and "email" properties by default. I wanted to allow users to log in via their email address or nickname. I tackled this in two steps. First, switch to logging in using the "email" property. Second, create a "nickname" property, and allow users to log in with either one. (Yes, I actually wanted to call it "nickname"; I probably could have just aliased it or something.)
In addition to the column-dropping migration, I needed to change the authlogic config to use the "email" property for user lookup, and to not validate the "login" property, since it no longer existed. This is handled in the acts_as_authentic configuration as discussed previously.
acts_as_authentic do |c| c.login_field = :email c.validate_login_field = false c.require_password_confirmation = false end
Lines two and three do what we expect. These configuration fields are documented in the login field configuration section of the authlogic docs. Part the first, done.
If we want to allow users to log in by either email or nickname we need to change both the user and user session classes (in addition to adding the "nickname" property to our user model, of course).
First we add a user class method to look up users by nickname or email.
If we want to allow users to log in by either email or nickname we need to change both the user and user session classes (in addition to adding the "nickname" property to our user model, of course).
First we add a user class method to look up users by nickname or email.
class User < ActiveRecord::Base # acts_as_authentic config removed for clarity. class << self def find_by_nickname_or_email(s) find_by_nickname(s) || find_by_email(s) end end end
In the user session class we'll define the user lookup method. While we're here, we'll genericize the error message to something not tied to property names.
class UserSession < Authlogic::Session::Base # Use authlogic's default message # generalize_credentials_error_messages true # Use our own message generalize_credentials_error_messages "Login info is invalid!" find_by_login_method :find_by_nickname_or_email end
This is documented in the session configuration section of the authlogic docs.
Users can now log in using either their email, or their nickname. As it stands, the sample app doesn't do much to enforce uniqueness (other than the authlogic defaults) of emails or nicknames.
(It also seems like we should disallow using someone else's email as a nickname, perhaps disallow emails-as-nicknames altogether.)
Users can now log in using either their email, or their nickname. As it stands, the sample app doesn't do much to enforce uniqueness (other than the authlogic defaults) of emails or nicknames.
(It also seems like we should disallow using someone else's email as a nickname, perhaps disallow emails-as-nicknames altogether.)
Sunday, September 04, 2011
Rails 3 + authlogic explorations
I've started a simple Rails 3 + authlogic example project on github, mostly for myself to experiment with. Right now it's basically a copy of this post's implementation (and unfinished at that), but I'll be expanding it over the next few days in various ways.
As it stands, the "application" (I use the term loosely) consists of a home page (root path) requiring login, and login/logout actions. You may follow the directions in the README file (and cut-and-paste some code), or fork it and do a DB create/migrate/seed, and have a user with a login ID of "plugh", password "xyzzy".
The example app will change its shape a bit as I poke authlogic with sticks, tailor the example to my specific needs, and see what all I can do with it. Ultimately, I'm not sure if I'll end up using devise, or if what I use depends on the app itself. (My own apps have different requirements than my work apps--I may use both!)
Part two; logging in using either a user's nickname or email.
As it stands, the "application" (I use the term loosely) consists of a home page (root path) requiring login, and login/logout actions. You may follow the directions in the README file (and cut-and-paste some code), or fork it and do a DB create/migrate/seed, and have a user with a login ID of "plugh", password "xyzzy".
The example app will change its shape a bit as I poke authlogic with sticks, tailor the example to my specific needs, and see what all I can do with it. Ultimately, I'm not sure if I'll end up using devise, or if what I use depends on the app itself. (My own apps have different requirements than my work apps--I may use both!)
Part two; logging in using either a user's nickname or email.
Monday, August 29, 2011
Associating has_many relationships in Rails 3 using checkboxes
(Not checkboxes, in my case, but a checkbox example is easy, and more common.)
Originally I thought I needed accepts_nested_attributes_for, but that seems to be mostly for when we're creating the related objects, which I'm not--I need to save relationships to existing objects.
My example (github) uses a simple product/category relationship. We need to get a product's categories, we need to get a category's products. In the olden days, we would have used habtm; this example uses has_many:through. We'll use checkboxes to associate categories on the product's form page.
A number of posts were helpful during this process, although none was an SSCCE (at least to my new-again-to-Rails eyes). I don't know if I ended up doing this in the (or a) Rails Way; feedback is welcome. The nutshell solution for me ended up boiling down to the following:
* I didn't need accepts_nested_attributes_for (I'm referencing, not creating).
* I needed attr_accessible to allow mass-assignments to the category IDs as per this post.
* This stackoverflow question provided form element hints.
* These posts pointed me away from habtm, although I'm still fuzzy on why (not _why).
* The second post above also reinforced form element naming.
The migrations for Product and Category are what we'd expect. The mapping table migration is similarly trivial.
The checkboxes are created with the snippet below. It's kind of "manual" this way, there's likely a cleaner way using stock Rails.
That's it: I suspect my original frenzied attempts, spinning through methodologies, got things out-of-sync and cost me a fair amount of time. Not needing attr_accessible is probably because I'm not using nested attributes. I'm not sure what's wrong with habtm. I'm not sure if there are performance penalties for using mapping classes. Lots to explore, which is both good and bad.
Originally I thought I needed accepts_nested_attributes_for, but that seems to be mostly for when we're creating the related objects, which I'm not--I need to save relationships to existing objects.
My example (github) uses a simple product/category relationship. We need to get a product's categories, we need to get a category's products. In the olden days, we would have used habtm; this example uses has_many:through. We'll use checkboxes to associate categories on the product's form page.
A number of posts were helpful during this process, although none was an SSCCE (at least to my new-again-to-Rails eyes). I don't know if I ended up doing this in the (or a) Rails Way; feedback is welcome. The nutshell solution for me ended up boiling down to the following:
* I didn't need accepts_nested_attributes_for (I'm referencing, not creating).
* This stackoverflow question provided form element hints.
* These posts pointed me away from habtm, although I'm still fuzzy on why (not _why).
* The second post above also reinforced form element naming.
(Note: I became uncertain about needing attr_accessible; the example works without it. In my original project it didn't, but it's likely I screwed something up the first time around and need to revisit it.)
Here's the Product, Category, and ProductCategory models, in all their tiny little glory.
class Product < ActiveRecord::Base validates :name, :presence => true has_many :product_categories has_many :categories, :through => :product_categories end class Category < ActiveRecord::Base validates :name, :presence => true has_many :product_categories has_many :products, :through => :product_categories end class ProductCategory < ActiveRecord::Base belongs_to :product belongs_to :category end
The migrations for Product and Category are what we'd expect. The mapping table migration is similarly trivial.
class CreateProductCategoriesTable < ActiveRecord::Migration def self.up create_table :product_categories, :id => false do |t| t.references :product t.references :category end add_index :product_categories, [:product_id, :category_id] add_index :product_categories, [:category_id, :product_id] end def self.down drop_table :product_categories end end
The checkboxes are created with the snippet below. It's kind of "manual" this way, there's likely a cleaner way using stock Rails.
<% Category.all.each do |cat| %> <%= cat.name %> <%= check_box_tag :category_ids, cat.id, @product.categories.include?(cat), :name => 'product[category_ids][]' %> <% end %>
That's it: I suspect my original frenzied attempts, spinning through methodologies, got things out-of-sync and cost me a fair amount of time. Not needing attr_accessible is probably because I'm not using nested attributes. I'm not sure what's wrong with habtm. I'm not sure if there are performance penalties for using mapping classes. Lots to explore, which is both good and bad.
Monday, August 22, 2011
Preventing double-clicks in jQuery/Ajax-y handlers
Throwing this out there to see what people thing--I suspect there are better, cleaner ways to do this, and I'm wondering what they are.
(I return false because I know I don't want to follow the link; it'd be cleaner to return the results of the function call.)
I use the the same way I use the normal jQuery click() function. As a hack it served its purpose; I'm curious what issues there are with it, and how real jQuery developers handle this.
Updated: This already exists in the jQuery API.
The documentation states that the handler is unbound after the first click. In my defense, originally the behavior was different after the first click--in other words, it wasn't as simple as one() allows, and I thought I was going to need the DOM element data. Which I didn't.
Oddly enough, my Google Fu failed me on this one, because this didn't show up on the first page of search results.
$.fn.click1 = function(fn) { $(this).data('clicked', false); $(this).click(function(o) { if ($(this).data('clicked')) return false; $(this).data('clicked', true); fn(o); return false; }); };
(I return false because I know I don't want to follow the link; it'd be cleaner to return the results of the function call.)
I use the the same way I use the normal jQuery click() function. As a hack it served its purpose; I'm curious what issues there are with it, and how real jQuery developers handle this.
Updated: This already exists in the jQuery API.
$("#foo").one("click", function() { alert("This will be displayed only once."); });
The documentation states that the handler is unbound after the first click. In my defense, originally the behavior was different after the first click--in other words, it wasn't as simple as one() allows, and I thought I was going to need the DOM element data. Which I didn't.
Oddly enough, my Google Fu failed me on this one, because this didn't show up on the first page of search results.
Tuesday, August 09, 2011
Rails 3 Custom Validator Quandary
I'm trying to figure out the "best" way to support discovery of custom validator errors on a Rails 3 model.
My model has both standard and custom validators. I need to check for validation errors from the custom validators, in both controller and template.
My slap-dash solution was to add an additional error message keyed to the field name suffixed with a validator-specific extension. For example, the standard message would be added to errors[:foo], and a custom message to errors[:foo__bar].
This works, but I really don't like it much--hence my plea for ideas. Here's the options I've considered so far, in no particular order.
(1) Add a model function that hides the logic. Easy, but vaguely discomforting.
(2) Check for the validator in the model's "initialize" method and if it's there, add an instance method. Similar to (1), but still far-removed from the validator.
(3) Expose something in the validator. Add it to the model class via an include, or dynamically in the model initializer.
(4) Have the validator add a method (foo_xxx? or something similar) to the instance at validation time. Only models that have been validated would have those methods, which gives me the heebie-jeebies.
(5) Pass in the class (instance?) in the EachValidator's options hash, and have the validator tweak the class (instance?) at init time. Having to pass in the class I'm already in seems janky, but...?
Updated:
(6) The "validates_with" also looks for a "setup" method in the validator (not an ActiveModel::EachValidator, but an ActiveModel::Validator) and passes "self" to it. "Sexy" validations use "validates_with" underneath, so this may be another approach.
The nature of my question, and my list of possible solutions, makes me wonder if I'm thinking about this the wrong way, and/or missing something *completely* obvious--am I?
My model has both standard and custom validators. I need to check for validation errors from the custom validators, in both controller and template.
My slap-dash solution was to add an additional error message keyed to the field name suffixed with a validator-specific extension. For example, the standard message would be added to errors[:foo], and a custom message to errors[:foo__bar].
This works, but I really don't like it much--hence my plea for ideas. Here's the options I've considered so far, in no particular order.
(1) Add a model function that hides the logic. Easy, but vaguely discomforting.
(2) Check for the validator in the model's "initialize" method and if it's there, add an instance method. Similar to (1), but still far-removed from the validator.
(3) Expose something in the validator. Add it to the model class via an include, or dynamically in the model initializer.
(4) Have the validator add a method (foo_xxx? or something similar) to the instance at validation time. Only models that have been validated would have those methods, which gives me the heebie-jeebies.
(5) Pass in the class (instance?) in the EachValidator's options hash, and have the validator tweak the class (instance?) at init time. Having to pass in the class I'm already in seems janky, but...?
Updated:
(6) The "validates_with" also looks for a "setup" method in the validator (not an ActiveModel::EachValidator, but an ActiveModel::Validator) and passes "self" to it. "Sexy" validations use "validates_with" underneath, so this may be another approach.
The nature of my question, and my list of possible solutions, makes me wonder if I'm thinking about this the wrong way, and/or missing something *completely* obvious--am I?
Monday, August 08, 2011
Bash function of the day
I push the command line on people, because it's generally the most efficient way to do several different things. My devs resist me, although I don't understand why... part of it is that most places I've worked at are Windows houses. No problem, say I: Cygwin to the rescue. Seriously, gang, I'm not just making this up.
(I'll grant, however, that there are some Finder/Explorer replacements that wrap up some of this functionality pretty nicely in a GUI, including having a command line in whatever directory is open--but it's still a command line, and you still should know how to use it.
Two things I do a lot from the command line are find files, and find files containing a pattern. Here's two drop-dead simple Bash functions that do this. Seriously, doesn't get much easier.
function fn {
find . -type f -name "$1"
}
(I'll grant, however, that there are some Finder/Explorer replacements that wrap up some of this functionality pretty nicely in a GUI, including having a command line in whatever directory is open--but it's still a command line, and you still should know how to use it.
Two things I do a lot from the command line are find files, and find files containing a pattern. Here's two drop-dead simple Bash functions that do this. Seriously, doesn't get much easier.
function fn {
find . -type f -name "$1"
}
function fng {
find . -type f -name "$1" | xargs grep "$2"
}
find . -type f -name "$1" | xargs grep "$2"
}
Friday, August 05, 2011
Mysterious Facebook JS error, resolved!
Two FB apps, both using FB.ui to publish to a stream. One worked, one didn't, nobody knew why. I needed to know.
Nutshell: FB JS code was inadvertently included twice (not by me, which always makes things more difficult). This caused the app_id and app_key values in the FB.ui call to go away--everything else was there, but two pieces critical (obviously) weren't. Note that the app code didn't explicitly do any initializations twice, the act of including the code twice was enough.
We ended up doing the FB share a different way due to time pressures, but before we did, we had multiple people looking at the app trying to figure out what was wrong. In retrospect, I think I should have diagnosed the problem a day earlier than I did, and there's a couple of reasons why.
The most obvious dumb thing was that I didn't look at the wire traffic and pick apart the FB.ui call's URLs. That was ignorance; this was the first FB issue I've ever tried to diagnose--I had no clue where to look. That step, however, ended up pointing me in the right direction (eventually).
I also should have created a blank sample app earlier, as a sanity check. This was partially inertia, partially time constraints, partially wasn't sure how easy/difficult it was to do, partially a belief that the FB pros would be able to figure it out, so I did other stuff and waited--didn't need to.
Once I had the working app, compared the generated URLs, things progressed rapidly. First I made the blank app structurally similar to the broken app: put the JS code into a partial (it's a Rails app), cut-and-pasted the init code, etc. I ignored styling--I made an assumption it was a JS issue fairly early on, and pursued that path first.
The blank app continued to work, so I switched my attention to the broken app, and stripped out everything except the FB code. Workie workie... made some additional guesses, started poking through the partials, and found a partial with two issues--a repeated div id used by the init code, and an include of the FB JS file.
I don't actually know if it was the include or the div id: the FB JS code is inserted dynamically as a div's child element--a div with the id that was repeated :/ Either way, it was broken HTML, and broken JS.
Here's what bothers me about this exercise: multiple people looked at this and couldn't figure it out. Identical code worked in other apps (we knew (or at least "thought strongly") it was something app-specific), the docs made it seem like what I was trying should work. It should have been easier to diagnose.
Code that fails sans clueage is a pain. Disallowing multiple loads of the same JS is easy, and it would have saved a few people a frustrating amount of time diagnosing what ended up being ridiculously easy, and (somewhat) obvious.
Wrong code should either be impossible, or act very wrong, not just somewhat, and quietly, wrong. That notion reminded me of an old Spolsky article which is tangentially related, but speaks to making things that are broken break in such a way it's obvious: fail fast.
Friday, July 01, 2011
Textmate and rvm, behave!
Several sites describe the process necessary to get TextMate to use either a wrapped (via the rvm wrapper command) or an rvm-specified default Ruby interpreter (including gemsets, basically anything you can use with the rvm use command).
Following just those steps never worked for me, even when I explicitly gave a wrapped TM_RUBY: it would still do an exec ruby in there and blow up. I've solved this (at least temporarily) by adding my ~/.rvm/bin directory to TextMate's PATH shell variable.
Sunday, January 30, 2011
Java/Struts Interview Back Online!
Aww yeah; it's back, YouTubier than ever. (The audio conversion is a bit spikey--I'll have to try it again I think.)
Watch it on YouTube!
Myeeeeah update! Update! myeeeeeeeeeaaaaaaaaa Update! Implementing googling!
For those keeping score at home... still haven't found anyone I actually *want* to hire, although there have been a few people that would have been solid workers, I really need someone thinking a bit more abstractly.
Watch it on YouTube!
Myeeeeah update! Update! myeeeeeeeeeaaaaaaaaa Update! Implementing googling!
For those keeping score at home... still haven't found anyone I actually *want* to hire, although there have been a few people that would have been solid workers, I really need someone thinking a bit more abstractly.
Subscribe to:
Posts (Atom)