To set up nested resources in Rails, I have seen example routes given like this:
map.resources :players
map.resources :teams, :has_many => :players
By doing this, you can visit teams/1/players and see a list. But it lists all players, not just those that belong to team 1.
How can I list only the resources that are associated with the parent resource?
You need to load the team first. A common practice is to do this in a before filter.
class PlayersController < ActionController::Base
before_filter :get_team
def get_team
#team = Team.find(params[:team_id])
end
def index
#players = #team.players # add pagination, etc., if necessary
end
def show
#player = #team.players.find(params[:id])
end
end
Note that the code above insists that you specify a team. If you want the same controller to work for both, you need to change it slightly (i.e. check for params[:team_id]).
You can use the excellent inherited_resources gem to DRY this up if you controller logic is straightforward.
The problem has little to do with map.resources and routing in general.
Note, players are not fetched magically by the framework: there's some action in some controller processing teams/1/players request and your code there fetches list of players to show. Examining that action (or posting here) should help.
Related
I'm new into rails and i have a question. I'm coding one Rails application for organize group attivity. I had realized a controller for Group Notification (when a new member was included into the group he/her recived one notification). I want to create a mechanism for notifications on new polls created. Do I need a new Controller or can i use the same?
If you really want custom controllers, you certainly can.
However, I find they make things more confusing as they don't follow the standard structure and I often forget about them and why I made them.
I'd do my best to stick to the practice of having controllers that match my models.
Here's the way I would structure your app:
Models:
Member (or maybe you call them users)
Group
Poll
Controllers:
Used for things like create, show, list, update, delete a record
MembersController
GroupController
PollsController
When doing more than basic CRUD operations, I focus on what is the "primary" or "parent" object and see if I can make it fit a CRUD operation.
So let's take your example of adding a member to a group:
If you have a form for a member where you can select an existing group, or maybe even create a new group, I would say member is the "primary" object and I would use the MembersController#create and MembersController#update actions. And I would use accepts_nested_attributes:
class Member < ApplicationRecord
has_many :groups
accepts_nested_attributes_for :groups
end
If you have the opposite (a group form where you add or remove members), I would flip this, using GroupsController#create and GroupsController#update with nested attributes.
class Group < ApplicationRecord
has_many :members
accepts_nested_attributes_for :members
end
This way your routes are very standard:
resources :members
resources :groups
resources :polls
You can use this same logic for notifications of polls.
Poll would be the primary object, so you could handle this logic within the PollsController#create and PollsController#update
If you want to break away from the RESTful routes, I would still use these controllers.
E.g.:
# routes
resources :members do
# the view with the form to choose a group
get 'choose_group', on: :member
# the controller action to receive the form data
post 'add_to_group', on: :member
end
# controller
class MembersController < ApplicationController
def choose_group
#member = Member.find(params[:id]
#groups = Group.all
end
def add_to_group
#member = Member.find(params[:id]
#member.groups << Group.find(params[:group_id]
if #member.save
redirect_to 'some/other/place'
else
render 'choose_group'
end
end
end
I'm making an online magazine style website and am having difficulties getting the syntax right for my final part of the project. The relationships are working as they should I am just having trouble calling the intended records.
Each post belongs to a category with category_id being the foreign key. When a user clicks this link, <%= link_to 'News', categories_path(:category_id => 1) %>, I'd like for them to be brought to an index page showing only posts with a category_id matching the parameter in the URL.
I've been messing around in the categories_controller.rb for almost two hours now with no luck. Anyone be so kind as to throw this noob a bone?
There are a few components of what you're trying to do. We'll start with the routing side, and make our way to the controller.
First, you need to make the proper routes. Since the post belongs to a category, you will need to have the category id in order to handle performing any sort of operations on the posts. So we'd need a route like /category/:category_id/posts/:id. Luckily, Rails has something to handle this. If you nest a resources within a resources, it'll generate these routes. So, we end up with this:
resources :categories do
resources :posts
end
And that will get you what you want in terms of routes. But now we have to actually implement it. So, we're going to need to take a look at the controllers. If you notice, all of those routes have a :category_id - so looking up the category shouldn't be too difficult:
class PostsController < ApplicationController
before_action :load_category
private
def load_category
#category = Category.find(params[:category_id])
end
end
Now, you have the category loaded, and it shouldn't be too difficult to implement the other methods from there:
class PostsController < ApplicationController
before_action :load_category
def index
#posts = #category.posts
end
def show
#post = #category.posts.find(id: params[:id])
end
# ...
end
In order to reference the Post index path, you'll have to use category_posts_path helper.
Your problem is that you're trying to use an existing route to handle some new functionality (for which it was incidentally not designed). That categories_path route is meant to take you to your category index.
You need to create a method in your controller to perform the functionality you want to see.
class PostsController < ApplicationController
...
def posts_by_category
#posts_by_category = Post.where("category_id = ?", params[:category_id])
end
...
end
Then you're going to need a view to display your #posts_by_category array (I'll leave this exercise to you).
And now for the key to your problem: you need a route pointing to the posts_by_category method.
get 'posts/posts_by_category' => 'posts#posts_by_category'
Now you should be able to create your link with the correct route:
<%= link_to 'News', posts_by_category_path(:category_id => 1) %>
I have a User model that has_many projects. The Project model belongs_to the User model. I am currently using the projects_controller.rb index action to display all of the projects that have been created across all users (Project.all).
On a separate page, I would also like a way to display all of the projects that belong to a specific user (i.e. go to page and be able to see all of the projects that belong to a given user).
I am having difficulty figuring out which controller/action/view to use and how to set up the routes because I am already used the index action for the projects_controller for the purpose of displaying all of the projects. Does anybody have any suggestions?
You could do /users/{:id}/projects, which would map to the users controller projects action. The route would have to be custom member action
resources :users do
member do
get 'projects'
end
end
rather than having different pages of listing. use same index page based on different criterias i.e. filters.
URL
match ":filter/projects", :to => "projects#index"
inside controller something like
case params[:filter]
when "all"
#projects = Project.all
when "user"
#projects = current_user.projects
when "batch"
# ..
else
# ..
end
How about you display the projects that belong to a particular user on the User#show page?
Something like this perhaps:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
# rest of class omitted...
Then you can access the projects that belong to the user in the view for the show page by calling #user.projects.
You should nest projects under users to get working code/paths like: /users/1/projects without any additional coding, so you need to change your resource lines in routes.rb to:
resources :users, :shallow => true do
resources :projects
end
and then under Projects#show action instead of Project.find(params[:id]) you need to get Project.find(params[:user_id])
That's seems to be correct
In my "routes.rb" file I have the following line:
resource :users
which gives me a bunch of named routes for accessing my User model in a RESTful manner.
Now, I've made some additions to the User model including creating a special class of user. These are still stored in the User model but there is a "special" flag in the database that identifies them as special.
So, is it possible to create special_users resource? For example, I'd like to have a "special_users_path" as a named route to "/special_users" which will return an index of only the special users when you perform a GET on the URL.
Is there a way to do this?
In Rails routing, a 'resource' refers to the standard 7 routes that are created for RESTful resources: index, show, new, create, edit, update and destroy. Normally that is enough, but sometimes you might want to create another action.
In the model, you want to create a scope that only returns special users:
class User < ActiveRecord::Base
scope :special, where(:special => true)
end
On the controller side, there are two ways to go about this. What you are suggesting is the creation of an additional action:
match "/users/special" => "users#special"
resource :users
In the controller, your special action would return the scope you just created:
class UsersController < ApplicationController
def special
#users = User.special
end
end
That will do what you ask, but I would suggest NOT doing it this way. What if you add other flags later that you want to search by? What if you want to search by multiple flags? This solution isn't flexible enough for that. Instead, keep the routes the way they are:
resource :users
and just add an additional line to your controller:
class UsersController < ApplicationController
def index
#users = User.all
#users = #users.special if params[:special]
end
end
and now, when you want to display special users, simply direct the user to /users?special=true
This approach is much more future-proof, IMO.
(This answer is assuming Rails-3. If you're still using 2.3 let me know)
You could set the special_users as a resource:
resource :special_users
If you need to point it to a special controller, you could specify it with:
resource :special_users, :controller => :users
But I would really suggest you to not creating another controller for retrieving a kind of user, but using a param to get them:
class UsersController < ApplicationController
def index
users = case params[:type].to_s
when "special"
User.special_users # Using named scopes
else
User.all
end
end
end
When you use the users_path to call the special users:
users_path(:type => :special)
Imagine you have two defined routes:
map.resources articles
map.resources categories, :has_many => :articles
both accessible by helpers/paths
articles_path # /articles
category_articles_path(1) # /category/1/articles
if you visit /articles, index action from ArticlesController is executed.
if you visit /category/1/articles, index action from ArticlesController is executed too.
So, what is the best approach for conditionally selecting only the scoped articles depending on the calling route?
#if coming from the nested resource route
#articles = Articles.find_by_category_id(params[:category_id])
#else
#articles = Articles.all
You have two choices here, depending on how much your logic and your view is tied to the scope.
Let me explain further.
The first choice is to determine the scope within your controller, as already explained by the other responses. I usually set a #scope variable to get some additional benefits in my templates.
class Articles
before_filter :determine_scope
def index
#articles = #scope.all
# ...
end
protected
def determine_scope
#scope = if params[:category_id]
Category.find(params[:category_id]).articles
else
Article
end
end
end
The reason for the #scope variable is that you might need to know the scope of your request outside the single action. Let's assume you want to display the number of records in your view. You need to know whether you are filtering by category or not. In this case, you simply need to call #scope.count or #scope.my_named_scope.count instead of repeating each time the check on params[:category_id].
This approach works well if your views, the one with category and the one without category, are quite similar. But what happens when the listing filtered by category is completely different compared to the one without a category? This happens quite often: your category section provides some category-focused widgets while your article section some article-related widgets and filter. Also, your Article controller has some special before_filters you might want to use, but you don't have to use them when the article listing belongs to a category.
In this case, you might want to separate the actions.
map.resources articles
map.resources categories, :collection => { :articles => :get }
articles_path # /articles and ArticlesController#index
category_articles_path(1) # /category/1/articles and CategoriesController#articles
Now the listing filtered by category is managed by the CategoriesController and it inherits all the controller filters, layouts, settings... while the unfiltered listing is managed by the ArticlesController.
This is usually my favorite choice because with an additional action you don't have to clutter your views and controllers with tons of conditional checks.
I often like to separate those actions. When the resulting actions are very similar you can separate the scopes inside the controller easy by seeing if params[:category_id] is present etc (see #SimoneCarletti answer).
Normally separating actions in the controller by using custom routes gives you most flexibility and clear results. Following code results in normal route helper names but the routes are directed to specific actions in controller.
In routes.rb:
resources categories do
resources articles, :except => [:index] do
get :index, :on => :collection, :action => 'index_articles'
end
end
resources articles, :except => [:index] do
get :index, :on => :collection, :action => 'index_all'
end
Then you can have in ArticlesController.rb
def index_all
#articles = #articles = Articles.all
render :index # or something else
end
def index_categories
#articles = Articles.find_by_category_id(params[:category_id])
render :index # or something else
end
Having only a single nested resource, using a conditional based on the params to determine it's scope would be the easiest approach. This is likely the way to go in your case.
if params[:category_id]
#articles = Category.find(params[:category_id]).articles
else
#articles = Article.all
end
However, depending on what other nested resources you have for the model, sticking with this approach can get quite tedious. In which case, using a plugin like resource_controller or make_resourceful will make this much simpler.
class ArticlesController < ResourceController::Base
belongs_to :category
end
This will actually do everything you'd expect. It gives you all your standard RESTful actions and will automatically setup the scope for /categories/1/articles.
if params[:category_id].blank?
# all
else
# find by category_id
end
I like to consider the action independent from the route. No matter how they get there, make a reasonable decision as to what to do.