Rails - overriding name route parameters - GLOBALLY - ruby-on-rails

I have a Rails(4.2.6) application that has tables like companies (id, serial...), user (id, serial, company_id...), where serial is a random generated 5-20 character long string and unique per table. What I am trying to achieve is to have routes like /companies/:serial and /users/:serial...
I have read the documentation, and can do the following in routes.rb:
resources :companies, param: :serial
resources :users, param: :serial
Now, that's not too DRY... Is there a way to do this globally? I know that I could have this in one line (resources :companies, :users, param: :serial), but I have other tables, in other namespaces, to which I would like to apply the rule also.
Another thing that I thought of was to have the serial as primary key but I prefer the auto-incrementing integer, and don't want to litter my db with columns like user.company_serial with 10-20 character long values...
I have tried to make a scope with param: :serial around my resources:
scope param: :serial do
resources :companies
...
end
like with path_names (read here in the documentation) but that didn't had the desired effect, instead added params[:param] with a symbol value :serial for some reason that I don't really understand.
I also know about the existance of the method to_param, but if I understood well, I should use it in the models, so I would have to write the same code as many times as many models I have.

The way you do this is submit serial in a URL as you would have done with the id param, params[:id] and use that in the controller.
Company.find_by_serial(params[:id)
You won't need to change routes, just the controller actions. This can be dried up, too.
You can do this by explicitly passing serial, or by adding the to_param method. If you add to_param to the model, it will always use this in every place it generates the route where it would have used the id and save you some work. If there is ever a place you would rather use the id (I prefer this in some controllers, like admin controllers), then you have to explicitly pass it or work around that.

Related

Rails 4: shallow resource not working with collection in RESTful routes

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.

RESTful nested controller in has_one relationship

Say I have a User that has_one ContactInfo.
An unrestful way to edit the contact_info would be to do this all through a single controller with a route of:
myapp.com/users/15/edit_contact_info
A more restful way would be to use two controllers, and route it like this:
myapp.com/users/15/contact_infos/23/edit
However, I don't like this, as the route includes the contact_info_id, which isn't really necessary for identifying the correct contact_info to update. Additionally, the contact_info_id is a confusing number for a user to see. (They may know their own user id, but the contact_info_id will seem like an arbitrary number).
Is there any way to RESTfully route like below:
myapp.com/users/15/contact_infos/edit
or something similar? Is this a bad idea?
resources :users do
get "contact_info/edit" => 'users#edit_contact_info'
end
I'd used a plural route, instead of a singular route. With the singular route, I get myapp.com/users/15/contact_info/edit.
Had:
resources :users do
resources :contact_infos
end
Changed to
resources :users do
resource :contact_info
end

Rails route architecture

I'm trying to understand the best way to architect a fairly simple relationship. I have a Job Model and a Category Model with a has_many relationship between them in a JobCategories model.
I'd like to have a page that lists all Jobs for a specific Category. Should the logic to pull this data be on the Category Controller (on the show action), or should I create a category method on the Job Controller? My gut tells me it should be on the Category side because a Category has Jobs, but it doesn't feel right that a Job would have the logic to pull all the Jobs for a given category.
Having said that, if I want the URL to be something that is more Job specific like:
domain/jobs/:id/{category-name} (for SEO purposes)
How would I structure the route so that it reads like the above, as opposed to
domain/categories/:id
which is what you'd get with resources :categories, only: [:show]
Thanks!
For a pretty slug, I'd suggest using FriendlyId on your categories model.
As for the routes, you will not get the desired route using resources :categories
One way to do it would be
resources :jobs, only: [] do
member do
get '/:slug' => 'categories#some_action'
end
end
the slug will be passed in your parameters.
This will yield a route like this
GET /jobs/:id/:slug(.:format) categories#some_action
UPDATE
the :slug is just an example for pretty url.
In your case you'd want to have :category_name. That would be passed into your controller through the params[:category_name].
One thing that I did start thinking when I re-read your question is that you want to show a list of jobs for a specific category. A url path like /jobs/:id/{category-name} shouldn't actually show a list of jobs as you are specifying an id which means a specific job. I think the url that you're looking to get is more along the lines of /jobs/{category-name}. Am I correct?
UPDATE 2
I suggest you read this Ruby On Rails Routing
UPDATE 3
Since you did want an url more like /jobs/{category-name}
You're routes should look like this
resources :jobs, only: [] do
collection do
get '/:category_name' => 'categories#some_action'
end
end
Good luck with your project! :D

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"

map.resources with alternate primary key(s)

I have a Rails model Object that does not have an ID column. It instead uses a tuple of primary keys from two other models as its primary key, dependency_id and user_id.
What I want to do is be able to do something like this in routes.rb:
map.resources :object, :primary_key => [:dependency_id, :user_id]
And for it to magically generate URLs like this:
/objects/:dependency_id/:user_id
/objects/:dependency_id/:user_id/1
/objects/:dependency_id/:user_id/1/edit
...Except that I just made that up, and there is no such syntax.
Is there a way to customize map.resources so I can get the RESTful URLs, without having to make custom routes for everything? Or am I just screwed for not following the ID convention?
The :path_prefix option looks somewhat promising, however I would still need a way to remove the id part of the URL. And I'd like to still be able to use the path helpers if possible.
You should override Object model's method to_param to reflect your primary key. Something like this:
def to_param
[dependency_id, user_id].join('-')
end
Then when you'll be setting urls for these objects (like object_path(some_object)) it will automatically gets converted to something like /objects/5-3. Then in show action you'd have to split the params[:id] on dash and find object by dependency_id and user_id:
def show
dep_id, u_id = params[:id].split('-').collect(&:to_i)
object = Object.find_by_dependency_id_and_user_id(dep_id, u_id)
end
You can also look at find_by_param gem for rails.

Resources