March 17, 2009
I'm converting an old, controller/action/id style Rails application to a more RESTful way of doing things, and ran into a brief roadblock: one of my main tables uses single table inheritance to generate three subclasses of items. I never actually use the superclass "task", I only use the three subclasses "action item", "work order", and "problem report".
So, I ran into this little challenge: all three STI subclasses use the same controller, "tasks", because they all have essentially the same behavior and differ only in minor details. But, when I do a resources map:
map.resources :tasks
Then I get errors in much of my code when I say things like redirect_to @task, because if that task happens to be an ActionItem, it's trying to call action_item_path(@task), which doesn't exist.
I googled around a bit to no result. Striking out on my own, it turns out the answer is as simple as mapping each resource independently, and just overriding the controller in map.resources:
In config/routes.rb
map.resources :tasks
map.resources :action_items, :controller => 'tasks'
map.resources :work_orders, :controller => 'tasks'
map.resources :problem_reports, :controller => 'tasks'
Now, redirect_to @task works just fine regardless of which subclass @task happens to be.
March 14, 2009
I've been following this excellent post by M. Hartl and this post by E. Chapweske banishing mass assignment from one of my Rails applications due to launch soon.
I'm following Chapweske's approach of blocking mass assignment by default in all models, by putting this line in an initializer:
ActiveRecord::Base.send(:attr_accessible, nil)
This had the expected side effect of breaking several zillion tests, because tests frequently use things like Model.build() and Model.create!() to generate on-demand fixtures during testing. Hartl has a great bit of code that creates unsafe_build() and unsafe_create() methods in ActiveRecord. You can use these methods instead of build() and create() to function as expected in your tests.
This works great, except that I also use the mass-assignment method update_attributes! in my tests and specs frequently, particularly when I want to spec the effect a change on one model has on an associated models' methods. So, I expanded on Hartl's helper code a bit, to give myself the necessary methods. In case it helps anyone else:
/lib/initializers/unsafe_build_and_create.rb
class ActiveRecord::Base
# Build and create records unsafely, bypassing attr_accessible.
# These methods are especially useful in tests and in the console.
def self.unsafe_build(attrs)
record = new
record.unsafe_attributes = attrs
record
end
def self.unsafe_create(attrs)
record = unsafe_build(attrs)
record.save
record
end
def self.unsafe_create!(attrs)
unsafe_build(attrs).save!
end
def unsafe_update_attributes!(attrs)
self.unsafe_attributes = attrs
self.save!
end
def unsafe_update_attributes(attrs)
self.unsafe_attributes = attrs
self.save
end
def unsafe_attributes=(attrs)
attrs.each do |k, v|
send("#{k}=", v)
end
end
end