Rails 4: shallow resource not working with collection in RESTful routes - ruby-on-rails

I have a nested resource as such:
resource :user, controller: :users do
# code ...
resources :profile, controller: :profiles, shallow: true do
# code ...
collection do
get :featured
end
end
end
The focus is the featured action of profiles.
The URL this generates is /user/profile/featured. I don't understand this because I specified shallow: true, yet it's still being nested under user/. I want the URL to be /profile/featured instead.
If I just do get :featured instead of putting it in a collection, I get /profile/:id/featured, which is also not what I want.

There are two types of routes within a given resource: collection routes (i.e. routes on the collection), and member routes (i.e. routes on individual records). When you specify that a nested route should be shallow, it only keeps routes nested that MUST be nested, and everything else is un-nested. Let's simplify your routes a smidge so we can talk through it better:
resources :users do
resources :profiles, shallow: true
end
If you take a look at the routes generated by that, you'll notice that the only profile-related routes nested under the user are:
GET /users/:user_id/profiles: Obtain a list of all profiles for this user
GET /users/:user_id/profiles/new: Render the page to create a new profile for this user
POST /users/:user_id/profiles: Create a new profile for this user
Everything else is no longer nested under users. Notice what all of these have in common: for this user. This comes back to what I said earlier: Rails only nests what it must. The user is an important part of the equation here, and there's no other way that it can be identified (without manual work on your part, anyway). However, once we have a record, we have a profile ID we can operate on. We no longer care about the user, and thus we don't need nested routes anymore.
In general, Rails expects that, since you've implied that profiles belong to users, it makes the most sense to only generate routes for operating on the collection within the scope of the user to which they belong. In other words, in most cases, you don't care about fetching the entire collection of profiles, you only care about the profiles for a particular user.
As a result, collection routes remain nested. On the other hand, for member routes we have a profile ID to work with, so they are not nested.
In your case, you're trying to do something a bit out of the above-described ordinary, so you'll need to create such routes yourself. Hopefully that explains the behavior you're seeing, though.
A final note, in case anyone notices: I used the plural resources here, where you actually used resource. It doesn't actually matter-- resource implies that there's a single user to operate upon when it sees a /user route, which is equivalent to the case with resources when it sees a /users/:user_id route. I used the plural form because I find it a little easier to understand.

Related

Should routes be nested for associations?

I have a one-to-many association between User and Occupation (a user has_many :occupations). In the routes file, I did:
resources :users do
resources :occupations
end
to nest the occupations routes inside the users. Playing around with AJAX requests, I realized it's easier for me to not have the occupations route nested, like this:
resources :users
resources :occupations
My question is, do I lose (performance, functionality) in any way by not having the routes nested?
Update: Aside from losing the users/1/occupations routing. I know that I won't get that if I don't nest the routes.
I wouldn't worry about the performance (if anything, the nested routes might be slightly slower) and just design the routes that make the most sense for your application.

Difference between resource and resources in rails routing?

what is the difference between resource and resources in rails routing
resource :geocoder
and
resources :posts
What is real difference between them ?
In essence, routing resources is when resources gives action abilities to a controller.
http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use
If a pluralized resources is used as a way to handle generic requests on any item, then a singular resource is a way to work on the current item at hand.
So in other words, if I have a collection of Apples, to retrieve a specific apple, I'd have to tell the router "Apples" what apple to retrieve by sending the ID of the apple. If I already have one Apple, then an ID is not needed.
Notice the differences between the two by looking at what actions (or routes) they have:
resources: Index, new, create, show, edit, update, destroy
resource: new, create, show, edit, update, destroy
In your example:
The controller "geocoder" is a singular resource that you can use to edit, create, update, etc.
The controller "posts", is a plural resource that will handle incoming generic posts that you can index, edit, create.. etc
Singular Resources:
Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like /profile to always show the profile of the currently logged in user.
Or, Normally your currently logged-In user belongs to a single organization, so to goto his/her organization profile page there can be two routes
#1
/organizations/:id
#2
/organization #simply
Here, the later implementation makes more sense; isnot it? you get the organization object from association
# in organizations#show
#organization = current_user.organization
To define such singular resource you use resource method: Example
# in routes.rb
resource :organization
creates six different routes in your application, all mapping to the Organizations controller:
whereas, you define plural resources using resources method
resources :organizations
http://guides.rubyonrails.org/routing.html#singular-resources
Sometimes, you have a resource that clients always look up without
referencing an ID. For example, you would like /profile to always show
the profile of the currently logged in user. In this case, you can use
a singular resource to map /profile (rather than /profile/:id) to the
show action.
A good way to see it is that resource does not have an index action, since it's suppose to be just one.
i think just the index view.
also there have been reported issues with routing with the resource helper and form helpers. personally, i use the syntax:
resources :someresource, except: :index
in order to avoid the reported bugs.

Find path of a nested or non-nested resource

Working in Rails 3.2, I a polymorphic Subscription model whose subscribable_type may or may not be a nested resource. I'm trying to display the full URL link in an email view, but have no knowledge whether or not that resource is nested.
When I try url_for #model on a nested resource, it fails, expecting url_for [#parent, #model]. Unfortunately, I do not know how to discover the parent as defined in the Routes table.
Is there a way to identify the route path for a nested resource? If I could match the model to a route, I could fill in the necessary IDs.
As of right now, I've defined a method in my models called parent_resource :model that can be traversed, but I'm hoping there's a better way.
Within my routes.draw:
resources :projects do
resources :topics do
resources :comments
end
end
resources :subscriptions
(I realize I shouldn't be nesting so deeply)
Edit: Additional Information
My Subscription model is a resource I use to manage notifications. Subscribable types are provided a link that toggles the subscription for that user on that subscribable_type / subscribable_id on or off.
I then go through a Notifier < ActionMailer::Base which is provided the Subscription instance, and mail the user.
Through that setup, I'm trying to get the full url of subscription.subscribable which may be a Topic or a Project.
I realize that I could hammer out the conditions in this small case through a helper method, but I am curious to know how one would approach this if there were dozens of nested model pairs.
You mention subscription but your routes are completely different. I'm guessing the routes you gave were just an example then. I would start with trying to get rid of the custom parent_resource method you created. You can probably do the same thing simpler with adding a belongs_to through and maybe with conditions if you need too:
belongs_to :projects, :through => :topics, :conditions => ['whatever your conditions are']
I'd have one of those per parent type so I can do things like:
object.project.present?
And from there I could easily know if its nested or not and simplify things by letting rails do the parent traversal. That ought to simplify things enough to where you can at least figure out what type of subscription you have pretty easily. Next, I'd probably add some matched routes or try to cram an :as => 'somename' into my routes so I can call them directly after determining the nested part. One option would be something like this:
match "projects/subscription/:id" => "projects#subscription", :as => :project_subscription
match "other/subscription/:id" => "other#subscription", :as => :other_subscription
And so its pretty obvious to see how you can just specify which url you want now with something like:
if #object.project.present?
project_subscription_path(#object)
else
other_subscription_path(#object)
end
This may not be the best way to accomplish what I'm doing, but this works for me right now.
This builds a nested resource array off the shortest valid route helper and generates a URL:
(Tested in rails console)
resource = Comment.first
resource_name = resource.class.to_s.downcase
helper = Rails.application.routes.named_routes.helpers.grep(/.*#{resource_name}_path$/).first.to_s.split('_')
built = helper.slice!(-2,2) # Shortest possible valid helper, "comment_path"
while !(app.respond_to?(built.join("_").to_sym))
built.unshift helper.pop
end
built.pop # Get rid of "path"
resources = built.reverse.reduce([]) { |memo, name|
if name == resource_name
memo << resource
else
memo << memo.last.send(name.to_sym) # comment.topic, or topic.project (depends on belongs_to)
end
}
resources.reverse!
app.polymorphic_url(resources) # "http://www.example.com/projects/1/topics/1/comments/1"

Rails 3 Resource routing for join models

I have a many-to-many relationship between users and teams (as a has_many :through), and I'm trying to setup a "team members" join model as a resource in the routes.
In my layouts I've setup a "team context form" that sets a session variable for the current_team, and I want the route for the team_members resources to be defined as /team_members/:user_id/show. Is there any way to do this with the resources :team_members in routes.rb?
I've tried using :path_names => {:action => "\some\url"}, however for actions that require an :id the router appends the route to be something like "\:id\some\url"
edit:
If you want to be able to edit the team membership, you could have
resources :users do
resources :team_members
end
and then, to edit the membership => /users/:user_id/team_members/:id/edit
And then you can do whatever you want in the team_members_controller.
Or as numbers1311407 said, just resources :team_members and you'll have all the rest routes to work with the team memberships.
Really don't want the standard /teams/:team_id/users/:id ?
If you really want /team_members/:user_id/show
You could just do
get "/team_members/:id/show" => "users#show"
But I dont think it's a good idea.
I wonder if what you're looking for is:
resource :team_members do
resources :users
end
The "resource" command creates a route where team takes no :id and would allow you to look up the team using your current_team session variable.
You'd get these path in your app:
/team_members # team_members#show
/team_members/users # users#index
/team_members/users/:id # users#show
In each case you're responsible for looking up current_team.
if the relationship is many-to-many then the route you're looking to write doesn't reference the team, unless this show page is intended to show all teams a user belongs to?
This would work out of the box if you assigned an ID to the join model and simply used its natural GET route, e.g. /team_memberships/:id
Edit: sorry I didn't read the 2nd paragraph carefully, if you are storing the team in the session you could (as suggested by someone else) set up team_members as a singleton resource and pull the team from the session when getting the member.
If it works in the app, though, considering team_membership as its own resource is probably more naturally RESTful.

Why should I pluralize controller name for RESOURCE

I understand that there is a convention, about controllers' names so it should be pluralised.
But why should I pluralize controller's name for resource?
So this is ok:
resources :apples
But this is not:
resource :apple, :controller => "apple"
Why just not?
resource :apple
resource is different from resources. It's used if you have just one.
As this guide explains, it's useful if you only ever reference one. If you have, for example, a profile that you never mention the id, you just assume the current user needs to access or edit their own profile.
You can mix these, too. So say you want users to be able to view each other's profiles, but also have a url for their own profile:
resources :profiles
resource :profile

Resources