Today I show how to easily solve an incredibly annoying (but small) problem in Rails Routing for Resources.
For my past few Rails projects, I've created a module called ModelLoader that I include in my Controllers to load requested Models from the database. For each of my models, I have a #load_xxx
and a require_xxx!
. For an app that contains People and Tickets, the model loader would look something like this:
module Utilities
module Controller
module ModelLoader
def load_person
safe_load :@person, Person, :person_id
end
def require_person!
require_exists! :@person, Person, :person_id
end
def load_ticket
safe_load :@ticket, Ticket, :ticket_id
end
def require_ticket!
require_exists! :@ticket, Ticket, :ticket_id
end
private
def safe_load(variable_name, klass, parameter_name)
begin
instance_variable_set(variable_name, klass.find(params[parameter_name]))
rescue ActiveRecord::RecordNotFound => e
nil #swallow it; must have a statement here for coverage to see the line
end
end
def require_exists!(variable_name, klass, parameter_name)
raise error_for(klass, parameter_name) unless instance_variable_get(variable_name)
end
def error_for(klass, parameter_name)
if params[parameter_name]
msg = "Could not find #{klass} with id #{params[parameter_name]}"
else
msg = "Parameter #{parameter_name} is required"
end
ActiveRecord::RecordNotFound.new(msg)
end
end
end
end
In my Controllers, I just do something like
append_before_filter :load_person, :only => [:foo, :bar]
This is almost perfect. The problem is that some of my actions are accessible via more than one route. In particular, a nested- and non-nested version of the same resource:
map.resources :people do |people|
people.resources :tickets
end
map.resources :tickets
gives routes like
/people/:id/edit
/people/:person_id/tickets/:id
/tickets/:id
That means that somestimes :id is a Person#id and sometimes it's a Ticket#id. This wreaks havoc on my model loader. (It's also a problem for Sutto's similar, but more elegant solution.)
The solution is simple:
module ActionController
module Resources
class Resource
def member_path
@member_path ||= "#{path}/:#{singular}_id"
end
end
end
end
Now all route segments have the class name in them:
/people/:person_id/edit
/people/:person_id/tickets/:ticket_id
/tickets/:ticket_id
3 comments:
If you just use a resource plugin like resource_controller or make_resourceful, it will take care of all of this for you, and then some.
Thank you for sharing to us.there are many person searching about that now they will find enough resources by your post.I would like to join your blog anyway so please continue sharing with us
Here I found some interesting and useful information ... It was nice visiting your blog.
Post a Comment