Is the best way to handle nested resource requests in your controllers?

If I found out about Rails 3, if it's not easy for me to do something, I'm probably wrong. Therefore, I am looking for help.

I have several models that are interconnected in many ways.

I can create associations in models without problems. My problem is how to create controllers to work with these relationships. I will try to give an example if you do not see where I am going with this.

For instance...

class Account < ActiveRecord::Base has_many :locations end class Contact < ActiveRecord::Base has_many :locations end class Location < ActiveRecord::Base has_and_belongs_to_many :accounts has_and_belongs_to_many :contacts end 

Say I have the above models. These would be my resources ...

 resources :accounts do resources :locations end resources :contacts do resources :locations end resources :locations do resources :accounts resources :contacts end 

So, just to make it a little shorter, let's say I need a list of all the locations for the account. The above routes would apparently be accounting / 1 / locations. Thus, landing me in the pointers # index.

I hope I have not ruined my example at this point, but the best way to create this action is because it really has several tasks ... at least for the locations for the account, contact, and all locations.

So, I get something like this ...

 class LocationController < ApplicationController def index if params[:account_id] @locations = Location.find_all_by_account_id(params[:account_id]) elsif params[:contact_id] @locations = Location.find_all_by_contact_id(params[:account_id]) else @locations = Location.all end respond_with @locations end end 

Update # 1: clarify, as I get some answers that suggest that I am changing my relationship with the model. I work with an outdated system in which I cannot change relationships at the moment. Ultimately, my goal is to clear the database and relationships, but so far I can’t. So I need to find a solution that works with this configuration.

+4
source share
3 answers

Your current approach is not DRY, and will give you a headache if you say, for example, you wanted to impose additional areas on the index; for example, pagination, ordering, or field search.

Consider the alternative: notice how your if / elsif / else conditionally just finds the search area to send find to? Why not transfer this responsibility to a method that does just that? Thus, simplifying your actions and removing redundant code.

 def index respond_with collection end def show respond_with resource end protected # the collection, note you could apply other scopes here easily and in one place, # like pagination, search, order, and so on. def collection @locations ||= association.all #@locations ||= association.where(:foo => 'bar').paginate(:page => params[:page]) end # note that show/edit/update would use the same association to find the resource # rather than the collection def resource @location ||= association.find(params[:id]) end # if a parent exists grab it locations association, else simply Location def association parent ? parent.locations : Location end # Find and cache the parent based on the id in params. (This could stand a refactor) # # Note the use of find versue find_by_id. This is to ensure a record_not_found # exception in the case of a bogus id passed, which you would handle by rescuing # with 404, or whatever. def parent @parent ||= begin if id = params[:account_id] Account.find(id) elsif id = params[:contact_id] Contact.find(id) end end end 

inherited_resources is a great gem to easily use scripts like this. Written by Jose Valim (from Rails). I believe it should work with HABTM, but to be honest, I'm not sure if I ever tried.

The foregoing is essentially related to how the resources are inherited, but it mainly works with magic behind the scenes, and you only rewrite methods if you need to. If it works with HABTM (I think it should), you can write your current controller something like this:

 class LocationController < InheritedResources::Base belongs_to :contact, :account, :polymorphic => true, :optional => true end 
+6
source

You do not have to provide multiple ways to achieve the same resource. There should be no 1-to-1 resource mapping for associations.

Your routes file should look like this:

 resources :accounts resources :contacts resources :locations 

The whole point of REST is that each resource has a unique address. If you really want to open only accounts / contacts from a given location, do the following:

 resources :locations do resources :accounts resources :contacts end 

But you absolutely must not provide both sub-accounts / locations and location / account routes.

+4
source

The way I see it is that since the accounts and contacts seem to have similar behavior, it would be wise to use the Single Table Inheritance (STL) and have a different resource like User.

So you can do it ...

 class User < ActiveRecord::Base has_many :locations end class Account < User end class Contact < User end class Location < ActiveRecord::Base has_and_belongs_to_many :user end 

Resources remain the same ...

 resources :accounts do resources :locations end resources :contacts do resources :locations end resources :locations do resources :accounts resources :contacts end 

Then you have the same access to places, regardless of the type of job.

 class LocationController < ApplicationController def index if params[:user_id] @locations = Location.find_all_by_account_id(params[:user_id]) else @locations = Location.find_all_by_id(params[:account_id]) end respond_with @locations end end 

Thus, your code will become reusable, scalable, supported, and all the other good stuff that we were told about is great.

Hope this helps!

0
source

All Articles