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
Get Time Object at Start of Day in a Particular Time Zone
Deleting the Current Session with Rack::Session::Cookie
Implementing Bayesian Classifier in Ruby
What Are Tainted Objects, and When Should We Untaint Them
Env: Ruby\R: No Such File or Directory
How to Create a Form in Rails Without Having to Use Form_For and a Model Instance
Trouble on Rendering a Template Passing a Local Variable
Passing Binding or Arguments to Erb from the Command Line
What's the Cleanest Way to Override Activerecord's Find for Both Models and Collections
How to Beautify Xml Code in Rails Application
Ruby Code Beautification, Split Long Instructions on Multiple Lines
Document Model Attributes with Yard
Can't Install Rmagick Gem on Ubuntu 13.04
Set Tag Attribute and Add Plain Text Content to the Tag Using Nokogiri Builder (Ruby)