Rails 3 - Best Way to Handle Nested Resource Queries in Your Controllers

Rails 3 - Best way to handle nested resource queries in your controllers?

Your current approach is not DRY, and would give you a headache if say, for example, you wanted to impose additional scopes on the index; e.g. pagination, ordering, or searching by a field.

Consider an alternative: Note how your if/elsif/else conditional essentially is just finding the lookup scope to send find to? Why not move that 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's 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 for cleanly handling scenarios like this. Written by Jose Valim (of Rails). I believe it should work with HABTM, but honestly I'm not positive if I've ever tried it.

The above exmaple is essentially how inherited_resources works, but mostly it works its magic behind the scenes, and you only overwrite methods if you need to. If it works with HABTM (I think it should), you could write your current controller something like this:

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

Routing nested resources in Rails 3

The best way to do this depends on the application, but in my case it is certainly Option B. Using namespaced routes I'm able to use a module to keep different concerns separated out into different controllers in a very clean way. I'm also using a namespace-specific controller to add shared functionality to all controllers in a particular namespace (adding, for example, a before_filter to check for authentication and permission for all resources in the namespace).

Rails 4 [Best practices] Nested resources and shallow: true

I don't believe that Rails offers any built-in way to have the URLs use the full hierarchy (e.g. /projects/1/collections/2) but also have the shortcut helpers (e.g. collection_path instead of project_collection_path).

If you really wanted to do this, you could roll out your own custom helper like the following:

def collection_path(collection)
# every collection record should have a reference to its parent project
project_collection_path(collection.project, collection)
end

But that would be quite cumbersome to manually do for each resource.


I think the idea behind the use of shallow routes is best summed up by the documentation:

One way to avoid deep nesting (as recommended above) is to generate
the collection actions scoped under the parent, so as to get a sense
of the hierarchy, but to not nest the member actions. In other words,
to only build routes with the minimal amount of information to
uniquely identify the resource

source: http://guides.rubyonrails.org/routing.html#shallow-nesting

So while this may not be REST-compliant (as you say), you aren't losing any information because each resource can be uniquely identified and you are able to walk back up the hierarchy assuming your associations are set up properly.

.each Statement Based on Nested Resources

In controller:

@users = User.joins(:post).where(posts: {category_id: @category.id})

In view

<% @users.each do |user| %>
<Your html code>
<% end %>

Always keep your business logic out of views. It would be suggested making that query as a scope in the model.

In user.rb

scope :post_users, -> (category_id)  {joins(:post).where(posts: {category_id: category_id})}

In controller

@post_users = User.post_users(@category.id)

Angular JS ngResource with nested resources

Read through my answer to your previous question, you should include the values for both the parameters in an object you pass as a parameter in the call, i. e.:

$scope.user_missions = UserMission.query({user_id: some_value, id: some_other_value});

Rails nested resource join security best practices

Do it by rails way...

current_account = Account.find params[:id]

@tasks = current_account.candidates.collect(&:tasks)

This will do your job.

rails query only if nested resource exists

Replace includes(:profile) with:

joins(:profile).preload(:profile)

joins will give you INNER JOIN, which will select only users that have profiles. preload will preload profiles that where found in joins (to avoid N+1 problem).
You can also move this joins into a separate scope in User model, e.g. with_profile.

Update action for nested resource in Rails

Simply load the preference and then perform an update.

preference = Preference.find_by!(user_id: user_id)
preference.update(preferences_request)

render json: preference

You have to deal with the case the query returns nil (because the user doesn't exist, for example). preferences_request must be a Hash of attributes => values. Of course, you may want to validate it as well and/or use the strong parameters feature to filter our attributes you don't want to be updated.

Nested (and Unested at-same-time) Resources better approach

You gave me an idea:

models/user.rb:

class User < ActiveRecord::Base
has_many :posts
attr_accessible :name
end

models/post.rb:

class Post < ActiveRecord::Base
belongs_to :user
attr_accessible :title, :user_id
end

controllers/posts_controller.rb:

class PostsController < ApplicationController
belongs_to :user # creates belongs_to_user filter

# @posts = Post.all # managed by belongs_to_user filter

# GET /posts
# GET /posts.json
def index
respond_to do |format|
format.html # index.html.erb
format.json { render json: @posts }
end
end
end

And now the substance:

controllers/application_controller.rb:

class ApplicationController < ActionController::Base
protect_from_forgery

def self.belongs_to(model)
# Example: model == :user
filter_method_name = :"belongs_to_#{model}_index" # :belongs_to_user_index
foreign_key = "#{model}_id" # 'user_id'
model_class = model.to_s.classify # User

class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{filter_method_name} # def belongs_to_user_index
if params.has_key? :'#{foreign_key}' # if params.has_key? :user_id
instance_variable_set :"@#{model}", # instance_variable_set :"@user",
#{model_class}.find(params[:'#{foreign_key}']) # User.find(params[:user_id])
instance_variable_set :"@\#{controller_name}", # instance_variable_set :"@#{controller_name}",
@#{model}.send(controller_name.pluralize) # @user.send(controller_name.pluralize)
else # else
instance_variable_set :"@\#{controller_name}", # instance_variable_set :"@#{controller_name}",
controller_name.classify.constantize.all # controller_name.classify.constantize.all
end # end
end # end
EOV

before_filter filter_method_name, only: :index # before_filter :belongs_to_user_index, only: :index
end
end

The code is not complex to understand if you have notions of Ruby metaprogramming: it declares a before_filter which declares the instance variables inferring the names from the controller name and from the association. It is implemented just for the index actions, which is the only action using the plural instance variable version, but it should be easy to write a filter version for the other actions.



Related Topics



Leave a reply



Submit