David Stevenson's blog
Interesting Things
has_manyandbelongs_toassociations can now automatically create back references each other, thanks to a Backport of :inverse_of from Rails 3 to rails 2.3.6. This allows us to keep our object graphs more correct and avoid situations where we have 2 copies of the same object because the object graph is walked in reverse. Here's how to use it:
class Parent < ActiveRecord::Base has_one :child, :inverse_of => :parent accepts_nested_attributes_for :child end class Child < ActiveRecord::Base belongs_to :parent validates_presence_of :parent end
Ask for Help
"We keep getting webrat thread exceptions running our integration specs with the rails integration runner: Thread tried to join itself. The error message varies with different versions of ruby 1.8.6 vs 1.8.7."
Anyone had this problem or know why?
"How do I skin an iphone mobile site to be the correct width so it's not 980px wide?"
<meta name="viewport" content="width = device width" />
*"We're trying to deploy some nginx configuration changes to EngineYard Cloud, what's the right way to do that?"
We've tried building custom chef recipes to solve this problem, but they run after nginx has already restarted, so are a poor solution to this problem. The better solution might be to check in configuration files into the application and symlink them into the nginx configuration directory using a before_symlink.rb hook in the /deploy directory.
*"We've got a
has_manyassociation where some of the child records are originally saved in an invalid state. When we later load the parent and ask it if it's valid, it returns true even withvalidates_associated. How can we get the desired validation behavior?"
Turns out that unloaded associations are not validated. Solution: load the association before calling .valid? on the parent. In general, you should also not create invalid objects, instead using a state variable to put them into a "draft" or "incomplete" state where they are still valid but not complete. Then remove that state and you'll see the errors required to finish that object.
Interesting Things
- When RubyMine 2.0.1 won't run your focused specs, try attaching rspec 1.2.9 to it rather than 1.3.x. It fixed this issue for one of our teams.
- Rubymine 2.0.2 came out today: can finally run focused contexts?! Also including bundler support! What's new
- We tried our Unicorn on EngineYard cloud: so far so good. It's still "experimental" but seems to work.
In order to accomplish some advanced search functionality, we've added a lot of named_scopes to our User model. This seems like a good idea, and well within the intended use for named_scopes. Unfortunately, we ran into issues with our :joins. We have a separate User and Profile model, but our advanced search scopes often needed both to make decisions. So we had some scopes that look like this:
class User
named_scope :verified {
:conditions => {:email_verified => true}
}
named_scope :answered_questions {
:join => "INNER JOIN profiles ON profiles.user_id = users.id " +
"INNER JOIN answers ON answers.profile_id = profiles.id"
}
named_scope :with_name { lambda { |name|
:join => "INNER JOIN profiles ON profiles.user_id = users.id",
:conditions => ["profiles.name LIKE ?", "%#{name}%"]
} }
end
Using these named_scopes, we wanted to dynamically construct a finder that would return the results the user was interested, such as: User.verified or User.answered_questions or even User.verified.answered_questions.with_name('Joseph'). The last scope caused issues, unfortunately, with table aliasing. The query ended up joining in the profiles table twice, in exactly the same way without renaming the table, so mysql rejects the query.
The easiest solution to this problem was to use only the hash form for :join clauses, such as :join => :profile. Rails correctly merges multiple consecutive join scopes that use hashes. If you need to use string joins (such as a LEFT JOIN rather than an INNER JOIN) or put a condition directly on your join, then merging goes out the window and the hashed form is immediately converted to a string and all consecutive joins are "merged" by appending them together.
We started by manually aliasing our scopes, but in some cases we were concerned about the amount of duplicate data this was causing in our queries.
We thought about creating a dependency framework for named_scopes, such that you could have a single :profile scope that other scopes were dependent on and it would only ever get added once. This seemed really difficult because of the way the with_scopes are constructed by named_scopes, there was no good place to keep track of these dependencies, and it would still cause problems if you had a manual with_scope, or :join in your find.
Finally we decided that rails fundamentally lacked the capability to deal with duplicate joins, and that we should solve this problem. It seemed a good solution was to allow :join options to take an array of strings as follows:
named_scope :answered_questions {
:join => ["INNER JOIN profiles ON profiles.user_id = users.id",
"INNER JOIN answers ON answers.profile_id = profiles.id"]
}
Now calling User.answered_questions.with_name('Joseph') will create three values in a :join array, two of which are identical and will be uniq'd out. The downside to this approach is that each value in the :join array has to be string identical, or it will not be properly uniq'd.
So if you are mixing hash style :profile joins with string joins of the same table you need to be careful you match the rails generated syntax. We mostly use string style joins to avoid this issue.
Here's the ticket the we filed and patched: 1077-chaining-scopes-with-duplicate-joins-causes-alias-problem
It has been commited and will roll out with rails 2.2. Since then we have filed two more issues related to :join and :include:
- 1078-using-include-assoc-and-join-assoc-leads-to-alias-issue
- 1104-references_eager_loaded_tables-should-search-tables-in-join-clauses
We hope to patch these two as well!
Joseph & David
