Custom parameter name in RESTful routing, Rails 3 [duplicate] - ruby-on-rails

I looked around on how to change the dynamic params slot and found this post that does the exact thing.
The post is https://thoughtbot.com/blog/rails-patch-change-the-name-of-the-id-parameter-in
Basically what it does is, if following is the routes:
map.resources :clients, :key => :client_name do |client|
client.resources :sites, :key => :name do |site|
site.resources :articles, :key => :title
end
end
These routes create the following paths:
/clients/:client_name
/clients/:client_name/sites/:name
/clients/:client_name/sites/:site_name/articles/:title
One solution is to override the def to_param method in the model, but I want this without touching the model itself.
But since its for Rails 2.x, how can I achieve the same for Rails 3?
Update
This app is using Mongoid. Not AR.
So, the gem friendly cannot be used afaik.

Rails 4 & 5
In Rails 4, the :param option was added, which seems to do exactly what you're looking for. You can take a look at the Rails 3 code compared to the Rails 4 code.
Details
You can easily implement this in your routes.rb file:
# config/routes.rb
resources :posts, param: :slug
# app/controllers/posts_controller.rb
# ...
#post = Post.find_by(slug: params[:slug])
# ...
As of the release of Rails 4, this functionality is documented in the Rails Guides.
Rails 3
Unfortunately, in Rails 3, the :key option for resources was removed, so you can no longer easily change the name for routes created in this way by just passing in an extra option.
Details
I assume you've already somehow gotten the application working the way you want in the past year, but I will go into a way to get the effect you describe in Rails 3 in routes.rb. It will just involve a bit more work than the to_param method. You can still define custom parameters in routes defined using scope and match (or it's cousins get, put, post, and delete). You simply write in the parameter name you want in the matcher:
get 'clients/:client_name', :to => 'clients#show', :as => client
scope 'clients/:client_name' do
get 'sites/:name', :to => 'sites#show', :as => site
end
You would have to manually add all the routes that resources automatically creates for you, but it would achieve what you're looking for. You could also effectively use the :controller option with scope and additional scope blocks to take out some of the repetition.
EDIT (May 8, 2014): Make it more obvious the answer contains information for both Rails 3 & 4. Update the links to the code to go to exact line numbers and commits so that they should work for a longer period of time.
EDIT (Nov 16, 2014): Rails 4 should be at the top now and include relevant information as it's been the current version of Rails for quite some time now.
EDIT (Aug 9, 2016): Reflect that the solution still works in Rails 5, and update outdated links.

in Rails 4, pass param option to change the :id params. For example
resources :photos, param: :photo_name will generate /photos/:photo_name

In Rails 3 you can rename the id keys by using a combination of namespaces and scopes like this (not very nice though):
namespace :clients do
scope "/:client_name" do
namespace :sites do
scope "/:name" do
post "/:title" => "articles#create"
...
end
end
end
end

If I understand you correctly, what you want is to have the client_name instead of id in your url, right?
You can do that by overriding the to_param method in your model. You can get more information here.

There's a gem for that, just like there's a gem for everything ;)
I've been using FriendlyId for this kind of behaviour in rails 3.
It will require you to add some code to your model though, like this:
class Client < ActiveRecord::Base
has_friendly_id :name
end
...and if your clients don't have URI compatible names, you might want to use a slug for that, which you can do with has_friendly_id :name, :use_slug => true. When using slugs you'll obviously need to persist them to the database as well though.
And as already mentioned, you can still use the to_param trick with rails 3, as documented here. I find FriendlyId a bit more versatile though.

Related

How to differentiate between :ids in routes/URL that belong to different models using FriendlyID?

I'm wondering how to generate a url that includes friendly ids for two different models. For example, if you have a post titled 'Rails Tutorial' and belonging to a particular User named 'Michael', and they each use a slug in place of their ID, how would you generate a url that looks like site.com/michael/rails-tutorial.
If you set your route to be:
get ':id/:id' => 'posts#show', as: 'show_post'
the URL will either be michael/michael or rails-tutorial/rails-tutorial, yet the way Friendly_id works from what I know is that your slug is represented by :id, so you can't configure it to work with get ':user_id/:post_id'.
I'm sure I just don't understand FriendlyID enough. Any help is appreciated.
In order to get the url you’re looking for, you’ll need to nest your routes:
resources :users do
resources :posts
end
This will give you the route:
localhost:3000/users/slug/post/slug
You’ll also have to extend FriendlyId in each model, add the slug to each model in a migration and use .friendly in the controller action(s).

Rails routes parameters for both :id and :token

Is it possible to set up a routing parameter for both :id and :token?
There are ample resources out there on how to change the default route params:
resources :events, params: :token
def to_param
token
end
However I cant seem to find anything related to an "either or" scenario that would allow someone to access Events#show by either the event_id or the event_token. In my head, the following makes sense:
resources :events, params: :token || :id
Backstory:
I am setting up a "sharing link" so users without an account can view an events page, similar to google docs. To do this, a unique token is generated for each event, creating a websafe url (Protected sharing link in Ruby on Rails). The issue I am facing is trying to maintain routing using both the event_id and the event_token since the event page is also being viewed by account holders, where finding events by id is much easier.
Thank you.
Assuming that the url is the same shape -
# routes.rb
resources :events
# your urls
/events/15
/events/some-token-string
Rails will treat everything after the last slash as the id parameter. From there you can just query using an or clause
# Rails 5 with no raw SQL
Event.where(id: params[:id]).or(Event.where(token: params[:id])).first
# Rails 4 and lower (also works with Rails 5)
Event.where("id = ? or token = ?", params[:id], params[:id]).first
Can't think of any built-in mechanisms, but you could use friendly_id gem to achieve this.

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"

Simple Acts_as_tree with nested_resources

Using Rails 3.1.1 and the gem acts_as_tree. I have googled the issue and checked similar questions here at SO (the answers are too old, or irrelevant).
I have a model called articles with a route that today looks like:
resources :articles, :path => '', :only => :show
resources :articles, :path => 'articles', :except => :show
I have three articles: "book", "chapter1" and "chapter2". Where book is parent to chapter1 and chapter2.
Today, my path to each article is: host.com/book, host.com/chapter1 and host.com/chapter2. I want the url path to be host.com/book/chapter1 and host.com/book/chapter2 , i.e. nested routes.
How can I create this in a clean simple manner?
Basically, I want a path that will be host.com/:parent_id/:parent_id/:id with N numbers of :parent_id. Pretty much how Wordpress-articles are routed.
I don't believe route globbers is the solution, but I might be wrong. It seems to give the same result for host.com/:id and host.com/foo/bar/:id which will result in duplicate content.
A)
If you have a solution for the routing and the only problem with it is that you're concerned about duplicate content issues, you could consider adding <link rel="canonical" href="..."> to the pages generated from those requests. It's not bulletproof though, as Google considers it a suggestion.
Not sure if the route globbers solution would take care of generating the URLs with parent IDs though.
B)
You don't need the parent IDs to perform the routing, correct? You just want to include them in the URLs and route those requests the same as if using the URLs like example.com/chapter1, correct?
If you'd consider a solution that's not purely at the Rails level, what about rewriting the URLs on those requests so that /:parent_id/:parent_id/:id becomes /:id before Rails processes it? That would be easier if there was a static prefix, like /articles/:parent_id/:parent_id/:id.
I imagine you'd need to write some helpers to generate the URLs with parent IDs for linking to those resources.
Duplicate Content
Either way, you'll need to generate URLs that include the parent IDs, so duplicate content issues probably aren't too likely if you only link to those resources using those URLs.
You have three "articles"... "book", "chapter1" and "chapter2" all represent same 'resources', named 'articles'. Same 'resource' cannot be nested. If you need nested routes you have to define separately parent resource and child resource. following code spinet may help you
class Book < ActiveRecord::Base
has_many :chapter
accepts_nested_attributes_for :chapters
end
class Chapter < ActiveRecord::Base
belongs_to :book
acts_as_tree :parent_id
end
match '*p1/*p2/*p3/.../*pn' => 'articles#show'
The ... is not literal, just define as many parameters as you need upto n.
URL:
host.com/book/chapter1
params[:p1] = 'book'
params[:p2] = 'chapter1'
params[:p3] = nil
URL:
host.com/book/chapter1/section2/sentence4
params[:p1] = 'book'
params[:p2] = 'chapter1'
params[:p3] = 'section2'
params[:p4] = 'sentence4'
params[:p5] = nil
That'd have to be your LAST route.
I think it would also make any catchall routes inoperable, but they're now commented out in the default routes.rb in Rails 3, If you use them, you'd have to manually specify all routes normally handled by the old style catchall routes.
And, if you have a controller named articles, you could never have a book titled 'articles' same with all your controllers, To be safe you probably have to rename all your controllers, i.e articles becomes X_articles. You could never have a book call X_articles then, and so on....
Totally untested.
What you're looking to do is use Rails for something it isn't made for. No matter what answer you get here, it either won't be RESTful, DRY, or make sense to be used with Active Record. Consider restructuring your idea, or bring your application to another platform if it's not too late.
Source to back up my claim: https://stackoverflow.com/a/174287/628859

User-friendly nested routes in rails

I am a ruby/rails newbie and have a belongs_to relationship between, let's say, group and user (user belongs_to group).
I would like to have the following type of url:
www.mysite.com/abcd/user/1 - (NOTE "group" is not part of the URL)
where abcd is the group name and 1 is the user ID.
Is this easy to do? How do I go about it?
Thanks!
I'm not sure, but try this:
scope :path => '/:group_name' do
resources :users
end
You must find your group by params[:group_name] if you use this approach.
Yes, it is very easy to do in both Rails 2 and 3.
Assuming you're using Rails 3, you would do it like this
match ":group/user/:id", :to => "group#user"
When you use the match method, URL parts with a preceding colon will become parameters, so in your controller you would have params[:group] and params[:id] available. The :to paramater defines the controller and the action, so in this example it would send the request to the GroupController and the user action. :to can actually point to any Rack application end point, the "cont#action" is just a shorthand.
If you are using resources, you can set those up as normal, and then just define this non-standard route somewhere else in the stack and both will work side-by-side.

Resources