We've had some trouble with test task errors causing failing builds on our continuous integration boxes ever since the release of the version 0.8.3 rake gem. Sound familiar? Read on!
Interesting Things
Aliasing ActiveRecord reader methods
ActiveRecord reader methods are not eagerly loaded, and as such cannot be aliased using alias_method. There are a number of workarounds to this, those talked about included: calling self.define_read_methods, after which all your read methods will be loaded and available for aliasing, or you can simply define the alias instead of using alias_method.
belongs_to and foreign keys.
If you specify an alternate foreign key when using belongs to, you will have access to that id via the name of the foreign key, and not the name of the belongs_to association. It may be worthwhile allowing access to the id via the belongs_to association in order to keep consistency.
boo
BOOOO!!!!! BOOOOOOOOO!
Interesting Things
- Removing class definitions with Object.send(:remove_const, :Foo)
Sometimes it is necessary to reload or replace a stub/test class during testing (apparently rspec is particularly susceptible to this). This can be achieved using Object.send(:remove_const, :Foo) where Foo is the camel-cased name of the class. If your class is defined within a module, it may be necessary to use Module.send(:remove_const, :Foo)
Ask for help
- foo.bar.reload versus foo.bar(true)
As a point of curiosity, does anybody know when has_many relationship reloading moved from (or to?) foo.bar(true) to foo.bar.reload? The reload syntax seems preferable for readability; will foo.bar(true) continue to work in the future?
This week, Damon and I were doing a performance optimization for some slow queries. The most performant solution involved denormalizing some data into a join table and doing a subselect to get the ids of the records we wanted. Not rocket science, but also a bit ugly to construct the SQL by hand. Our solution was to cheat a tiny bit and use an ActiveRecord internal method to generate the SQL for us.
def favorite_posts(options={})
subselect = Favorite.send(
:construct_finder_sql,
:select => "post_id",
:conditions => {:blog_id => self.id},
:order => "published_at DESC",
:limit => options[:limit] || 10, :offset => options[:offset])
Post.find(:all, :conditions => "posts.id IN (#{subselect})", :order => "published_at DESC")
end
That code uses the private method Favorite.construct_finder_sql to generate the following SQL:
SELECT * FROM posts WHERE posts.id IN (
SELECT post_id FROM favorites WHERE blog_id = 42 ORDER BY published_at DESC LIMIT 10 OFFSET 10
) ORDER BY published_at DESC
The Ruby may look like more code than the SQL, and in that form it is... but if you go the hack up a string route, once you start using string operations or interpolation to deal with the variable parts of the query it gets ugly pretty fast. Using the ActiveRecord code to put it all together keeps it nice and clean, and even makes sure things are sanitized and quoted properly too.
Interesting Things
- has_many_polymorphs preloads models
has_many_polymorphs will scan through the models directory for any files that include the plugin and preload them. This can cause problems if these models depend on other plugins (that have not yet been loaded) to operate correctly. The has_many_polymorphs source mentions this behavior in a comment and includes a fix:
Searches for models that use has_many_polymorphs or acts_as_double_polymorphic_join and makes sure that they get loaded during app initialization. This ensures that helper methods are injected into the target classes. Note that you can override DEFAULT_OPTIONS via Rails::Configuration#has_many_polymorphs_options. For example, if you need an application extension to be required before has_many_polymorphs loads your models, add an after_initialize block in config/environment.rb that appends to the 'requirements' key:
Rails::Initializer.run do |config|
# your other configuration here
config.after_initialize do
config.has_many_polymorphs_options['requirements'] << 'lib/my_extension'
end
end
Instead of using config.has_many_polymorphs_options, we were able to achieve the same effect by simply dropping our required plugins into the after_initialize block. If we decide to remove has_many_polymorphs in the future, the config file should still run correctly. It's worth noting that the UltraSphinx search plugin operates in a similar fashion.
- returning JavaScript in an Ajax IFrame Method (AIM) call
If you are using AIM for file uploads, you may find that any content returned to the IFrame is escaped (as if it were regular HTML). On some browsers, this occurs regardless of what the content-type is set to. A workaround is to embed any content that you don't want escaped into a comment tag; after the request completes, you can use JavaScript to retrieve the contents and optionally eval them.
Ask for help
- Ajax click_and_wait for Selenium?
assert_text_present offers a simple way to test Ajax behaviors in Selenium, as it will wait 5 seconds (during which the Ajax callback can complete) while looking for the specified text (which can be the body of your Ajax response). Another strategy with which we've had some luck is to write a custom method that will wait a specified amount of time before continuing (particularly useful if your Ajax call takes a lengthy amount of time to complete).
Interesting
Until a recent commit, Polonium defined a method on Module named
deprecate, which (at least in our case) overrode the method of the same name defined in Rails. This caused some code in Rails to blow up at class loading time when trying to call Rails'deprecatemethod with the wrong number of arguments.We changed the name of Polonium's method so it wouldn't clash with Rails, and checked this into the master branch of Polonium on GitHub.
As of Rails 2.1, ActiveRecord support partial updates, which means that it's smarter about what fields get updated in the SQL UPDATE statement issued by
save. If you haven't updated any fields in the object, no update will happen at all, no AR callbacks will fire, etc. If you depend on these things to happen even when not updating any fields, you can callwill_change!on the model object. It was floated that aforce_savemethod might be helpful.It is often claimed that
Symbol#to_procis to slow to use in production code. But a clever Rubyist out there found a way to speed it up by 4x.The test run for one of our projects here runs in 223 seconds on OSX, but in in 157s on Ubuntu. We'd like to figure out why (we're pretty sure we're using the MySQL Gem rather than the built-in MySQL driver that comes with Rails)
Git4Idea is now available through the IntelliJ Plugin manager. No more hand installation!
ActiveRecord::Base#to_parammust return a String if you override it. If you return an Integer you will get a weird "Can't convert Fixnum to String" exception somewhere deep in the routing code. You can't even tell where the exception is coming from due to the routing code's bizarreness.
Interesting Things
- before_save and after_save may not perform as you expect!
Previously, returning false from before or after filters callbacks would halt the chain entirely. This is no longer the case, except with before_save and after_save -- returning false in a before_save will stop the filter callback chain and prevent a save, returning false in an after_save will stop the filter chain silently, and NOT rollback the save. This can be troublesome if you have after_save filters callbacks you expect to execute, especially if you have conditional clauses in an after_save, as they can easily return a false value where you did not intend it.
Please see Brandon Keepers' clarification regarding filters vs. callbacks below -- thanks Brandon!
Ask for help
- Bootstrapping data into an existing system?
Using migrations for one-time data inserts can be problematic (for example, when changing seed data; additionally, it's advisable to create a database from the schema.rb rather than migrations) , it can be useful to create a class that is responsible for inserting the seed data into the database. This class can be wrapped in a capistrano or rake task. The db-populate plugin is designed to aid in this task.
- Multiple Routes/One controller
It is possible to point multiple routes at a single controller, however, this situation may suggest a need for multiple controllers, possibly inheriting from a single base controller containing any shared logic. This prevents a excessive switching logic in controllers, which can help minimize the possibility of argument-related bugs.
This release features a sexier Double definition block syntax using instance_eval:
mock(User) do
find(1) {user_1}
find(2) {user_2}
end
RR also maintains its non-instance_evaled Double definition block syntax, if the block has an arity of 1:
mock(User) do |expect|
expect.find(1) {user_1}
expect.find(2) {user_2}
end
The instance_eval syntax is very useful in the context of Double Definition chaining:
mock.proxy(User).find(1) do
mock.proxy! do
blogs do
mock.proxy!.find_by_id(2) do |actual_blog|
actual_blog.name.should == "My ranty blog"
actual_blog
end
end
fans do
mock.proxy!.thank_everybody
end
foes do
mock.proxy!.ask_for_forgiveness
end
end
end
user = User.find(1)
user.blogs.find_by_id(2) # My ranty blog
user.fans.thank_everybody
user.foes.ask_for_forgiveness
Of course the previous example is a complicated case of interaction testing, and a simpler state-based and/or hybrid approach may be better, but it demonstrates how using instance_eval can promote readability.
Here is the non-instance_eval solution:
mock.proxy(User).find(1) do
mock.proxy! do |expect|
expect.blogs do
mock.proxy!.find_by_id(2) do |actual_blog|
actual_blog.name.should == "My ranty blog"
actual_blog
end
end
expect.fans do
mock.proxy!.thank_everybody
end
expect.foes do
mock.proxy!.ask_for_forgiveness
end
end
end
instance_eval Controversy
Ola Bini warned against overusing instance_eval. While instance_eval adds beauty, it can also make things more difficult to extend and debug.
In RR, the DoubleDefinitionCreatorProxy (the object that is instance_evaled when defining Doubles using blocks) uses the blank slate pattern, so arbitrary method names can be passed in to define the Double. The Blank Slate implies that the DoubleDefinitionCreatorProxy will not be extended with methods. James Earl Gray explains the pattern that I used for RR.
spec/test helper methods will not be usable within the instance_evaled blocks because the intent of the DoubleDefinitionCreatorProxy object is incompatible with the instance_eval with delegation pattern. DoubleDefinitionCreatorProxy already uses method_missing to create DoubleDefinitions. If you wish to use spec/test helper methods, you will need to memoize it to a variable and use lexical scoping.
Now, one may want to define a helper method in the test/spec that is returned when the Double is invoked.
describe User do
describe "#fans.thank_everybody" do
it "thanks all of my lovely fans" do
user = User.new
memoized_my_lovely_fans = my_lovely_fans
mock(user) {fans {memoized_my_lovely_fans}}
memoized_my_lovely_fans.each {|fan| mock(fan).thank_you}
user.fans.thank_everybody
end
def my_lovely_fans
[Fan.new, Fan.new]
end
end
end
While I think this will be a good addition to RR, I recognize that adding the instance_eval has the possibility of making RR less usable. I'll pay close attention to see how this pans out and am willing to remove it if there are major issues.
You could do this:
Person.tall_people.length.should == 3
Person.tall_people[0].should == people(:linda)
Person.tall_people[1].should == people(:dwane)
Person.tall_people[2].should == people(:rick)
This is better, because it's clearer, and because in one stroke you prove bounds, content, and order:
Person.tall_people.should == [
people(:linda),
people(:dwane),
people(:rick)
]
I argue that this is best:
Person.tall_people.collect{|p|p.first_name}.should == [
"Linda",
"Dwane",
"Rick"
]
Failures messages are a pleasure to read
expected: ["Linda", "Dwane", "Rick"],
got: ["Juliette", "Jeanne"] (using ==)
And code is clearer overall (the price is a "collect")
Collecting away from the original object (and into primitives) is not only clearer, in most cases your aim is not to re-prove that the element objects are fully and properly configured. You've already done that elsewhere. You only want to prove, in the simplest possible terms, that the group of things you got is what you expected to get.
Let's say you don't intend to care about order. Sorting on primitives is a snap:
Person.tall_people.collect{|p|p.first_name}.sort.should == [
"Dwane",
"Linda",
"Rick"
]
Slightly more controversial:
Person.short_people.collect{|p|p.first_name}.should == []
The collect seems silly at first glance, but you're making present and future assertion failures much friendlier. You'll be happy about brain cycles saved and sanity kept during big refactorings.
Interesting Things
We have a project that generates a lot of highly precise floating point numbers. However, we primarily want to display these numbers with only two decimal places of precision. In addition, we want to display these numbers with standard comma delimiters to the left of the decimal point.
Sadly, the Ruby #sprintf method provides the former functionality, but not the latter. What to do? Use a Rails helper, of course.
The NumberHelper from ActionView provides some useful functionality, so we used that. As it turns out, we found the best way to get the formatting we want to be using the #number_to_currency function with no denomination.
Also, rather than mixing the entire helper into the Float class for just one method, we chose to mix the helper into a nested class and expose only the functionality that interests us. The result looks something like this:
class Float
class RailsNumberHelpers
extend ActionView::Helpers::NumberHelper
end
def formatted
s = Float::RailsNumberHelpers.number_to_currency(self, :unit => '', :precision => 2).chomp('0')
end
end
