Blog
what did i learn today

They default way in rails 4 to add foreign keys is simply

add_reference :people, :user

And this will add a column user_id to the people table, if the table exists. I have been looking in the rails code where this is handled, and it is really straightforward.

Note that standard rails does not really do anything for referential integrity, it creates the correct columns and an index if so specified.

But we use the schema_plus gem, for two reasons:

  • it does handle referential constraints correctly (on the database level)
  • it allows to specify the creation of views in a migration more cleanly

So, with schema_plus, if you write something like:

add_reference :people, :owner

and there is no owners table, this will give an error.

So instead you need to write :

add_reference :people, :owner, references: :users

This will correctly generate an owner_id which is a foreign key to users(id).

If you want to create a self-referential link, that is really easy. Just write

add_reference :people, :parent

This will create a self-referential link. Awesome :)

For completeness, the add_reference will add a reference columns to an already existing table. The same can be done when creating (or changing) a table with the following syntax:

create_table :people do |t| 
  t.references :owner, references: :users
  t.references :parent
end 

So, in short, if you were not using the schema_plus gem already, start doing so now :)

There are different reasons why a ActiveRecord::StaleObjectError would occur. Sometimes it is caused because rails does not have a stable identity map yet. This means that if the same object would be retrieved via different associations, your rails process would not know they point to the same object, would keep different copies of the same object and that could easily cause a StaleObjectError if you attempt to change both. Such an occurrence needs to be fixed structurally in your code. But sometimes you can get StaleObjectError because things happen at the same time. There are two ways to handle this:
  • make sure that controller actions which affect the same object, are always executed sequentially. One way to achieve this is to lock the object you want to update in the database. Then all other processes wanting to update the same object will have to wait until the lock is released (and the lock can be acquired). This is a valid approach but costly, intrusive (you need to explicitly add code), and possibly dangerous. It is normally easy to avoid, but you have to be careful for deadlocks and locks that are never released.
  • when a StaleObjectError occurs, just retry the request
Now that second option seems valid, if only it would be easy to handle. Luckily there is a very easy and unobtrusive way to automatically retry all requests when a StaleObjectError occurs. Let's just create a middleware that catches the exception and retries the complete request a few times. Create a new file lib/middleware/handle_stale_object_error.rb containing the following: [ruby] module Middleware class HandleStaleObjectError RETRY_LIMIT = 3 def initialize(app) @app = app end def call(env) retries = 0 begin @app.call(env) rescue ActiveRecord::StaleObjectError raise if retries >= RETRY_LIMIT retries += 1 Rails.logger.warn("HandleStaleObjectError::automatically retrying after StaleObjectError (attempt = #{retries})") retry end end end end [/ruby] Then in your config/application.rb you need to add the following lines at the bottom to activate the middleware (this requires a restart of your rails server): [ruby] # # Middleware configuration # require 'middleware/handle_stale_object_error' config.middleware.insert_after ActiveRecord::SessionStore, Middleware::HandleStaleObjectError [/ruby] Warning: this code will retry all occurences of StaleObjectError. For some occurrences this will not help at all, so you have to use your own judgement if this middleware is something you want in your codebase. I like this approach because it is an "optimistic" approach: it only adds extra processing when a StaleObjectError occurs, and when a not-fixable StaleObjectError it still fails as it should. Hope this helps.
rewriting find_by_sql for rails 3
I am starting in Rails 3. I have a simple situation where i would select a parent based on a condition of one of its children. Normally i would write something like: [ruby] Distributor.find_by_sql("select d.*, c.rate from distributors d, coverages c where c.country_code_id = 25 and c.distributor_id=d.id order by rate asc") [/ruby] Although this works, it is not the best solution to take. For one, it is vulnerable to sql-injection and secondly it shows a lot of the underlying database structure, and on top of that the sql-implementation could change between databases. In Rails 3 i can now write [ruby] Distributor.includes(:coverages).where("coverages.country_code_id=? and distributors.id =coverages.distributor_id", 25).order(:rate) [/ruby] This is so readable! Awesome :)