Correct method for custom Rails routing - ruby-on-rails

In my routes I currently have resources :users and so I get the routes such as /users/id/ and /users/id/edit and so on...however, I am wanting to have the 'default' URLs for my pages begin with /name where name is the user's unique login name.
So, right now my routes file looks like this.
resources :users
match '/:name' => 'users#show_by_name'
Then in my users_controller.rb file, I have methods defined as such...
def show_by_name
#user = User.find_by_name(params[:name])
render 'show'
end
So, basically it is doing the same thing as def show but instead of an id being passed in the URL, it's a name.
In my views I am linking like this...
<li><%= link_to "My Profile", "/#{current_user.name}" %></li>
As opposed to using <li><%= link_to "My Profile", current_user %></li>
I am wondering if I am going about this the correct way. I feel like I am doing something unnecessary by using extra methods in my users_controller.
Would I be better off just removing the resources :users line and creating my own routes that are more suited towards the type of URLs I want on my application?
Thanks for your time.

You might be better off overriding the to_param method in your User model. Rails has in built function for search friendly URL's
class User < ActiveRecord::Base
def to_param
"#{user.name}"
end
end
Url's will generate as
user_url(#user)
#http://0.0.0.0:3000/users/andrew
# Controller
#user = User.find_by_name(params[:id])

I would advice you to use FriendlyID, it's a neat gem that translates the :id to a value based on one of the table's columns. This could be a title for instance or name in your case.
I found it fairly easy to start using.
Ryan Bates talks about it in this screencast: http://railscasts.com/episodes/314-pretty-urls-with-friendlyid
For installation look here: https://github.com/norman/friendly_id

Both Andrew and Martin are right (the FriendlyID gem actually uses the to_param override method), but you're asking two questions :
can I use another attribute instead of the default id as the id in the route ?
can I use a non-resourceful route ?
In both cases, the answer is yes. You may override the to_param method AND use a non-REST route such as :
match '/:id' => 'users#show'

Related

Including attributes in custom Rails routes

I hope the title is not to misleading, as I don't know a better title for the problem I'm working on:
I have a doctor which belongs to location and specialty. I'd like to route to show action of the doc controller like this:
/dentist/berlin/7
I defined my routes like this:
get ':specialty/:location/:id', to: 'docs#show'
And in my views create the following url to link to the show action of the doc controller:
<%= link_to doc.name, "#{doc.specialty.name}/#{doc.location.name}/#{doc.id}" %>
Is this a good solution to the problem? If not, is there a cleaner way to construct urls like this possibly using resources? What the heck is the name for a this problem?
Thank your very much for your help in advance.
For references, you should have a look at this page (especially the end of section 2.6)
If it is only for a single route, it's okay as you did. But then if you want to have more than one route (like /dentist/berlin/7, /dentist/berlin/7/make_appointment, etc.) you might want to structure a bit more your routes so as to take advantage of rails resources.
For example, instead of
get ':specialty/:location/:id', to: 'doctors#show'
get ':specialty/:location/:id/appointment', to: 'doctors#new_appointment'
post ':specialty/:location/:id/appointment', to: 'doctors#post_appointment'
You could have something like this (the code is almost equivalent, see explanation below)
resources :doctors, path: '/:specialty/:location', only: [:show] do
member do
get 'new_appointment'
post 'create_appointment'
end
end
Explanation
resources will generate the RESTful routes (index, show, edit, new, create, destroy) for the specified controller (doctors_controller I assume)
The 'only' means you don't want to add all the RESTful routes, just the ones specified
Then you want to add member actions, ie. actions that can be executed on a particular item of the collection. You can chose different syntaxes
resources :doctors do
member do
# Everything here will have the prefix /:id so the action applies to a particular item
end
end
# OR
resources :doctors do
get 'new_appointement', on: :member
end
By default, the controller action is the same as the path name you give, but you can also override it
member do
get 'appointment', action: 'new_appointment'
post 'appointment', action: 'post_appointment'
end
Rails has some wonderful helpers when it comes to routing !
The correct approach is to give your route a name, like this:
get ':specialty/:location/:id', to: 'docs#show', as: 'docs_show'
Then you can use it like this:
<%= link_to doc.name, docs_show_path(doc.specialty.name, doc.location.name, doc.id) %>
Note 1:
Rails appends _path at the end of the route names you define.
Note 2:
You can see all the available named routes by executing rake routes.

Routing in rails - When to route to a crud method and when to route with the given parameter

I am in need of a little help regarding routing in rails. Traditionally if I have a route in routes.rb
resources:categories
it will match www.website.com/categories/id were id is an integer. But what happens if I wanted to route to a specific user or category like so:
www.example.com/categories/apple
instead of the traditional:
www.example.com/categories/4
I currently have these two lines in my routes.rb
match 'categories/:name' => 'categories#show'
resources :categories
so www.example.com/categories/apple will route to the show method in the categories controller correctly.
but
What if I want to create a new category? Here's the problem
www.example.com/categories/new
will route to the show method, and will not route to the new method
I can place an if statement in the show method checking is params[:name] == new, but I feel that there must be a better way to solve this problem.
My end goal is to route based on the string of the category (apple) and not based on it's ID (4) but also be able to create, update, and destroy a category.
Any tips?
Thanks!
you can pass a name as a parameter, for example you can try
link_to category_path(category.name) instead of link_to category_path(category)
and it will go to categories#show in controller, the route will look like example.com/categories/categoryName, in the show action of your cateogries controller, params[:id] will have the name, you'll need something like
Category.find_by_name params[:id] instead of Category.find params[:id]
Hope this helps you.
As #pdoherty926 says in the comment, you could use FriendlyId. If you want to manage it manually, you don't have to overwrite the routes, just put the needed code in the :show action:
class CategoriesControllers < ...
...
def show
if /^\d+$/ =~ params[:id]
# ... normal workflow for integer id
elsif /soe_regexp_that_matches_names>/
# ... do your thing with names
else
raise 404
end
end
...
end
I think the most simple way to do this your way is to define a route before your
match 'categories/:name' => 'categories#show'
like this:
match 'categories/new' => 'categories#new'
match 'categories/:name' => 'categories#show'
the order matters.

rails 3 dynamic routes

An application I am working on in Rails 3 has a City model. Each City has a "name" column. I want this City.name to be the base url.
For example, with the City Name "Los Angeles": http://myapp.com/los-angeles.
If I use
match '/:name' => "cities#show"
resources :cities
I can get http://myapp.com/Los%20Angeles, but I need it to be 'los-angeles'. I assume I need to change the to_params in the city.rb file.
Additionally, what should my link_to look like when this is working? Right now I am trying
<%= link_to "View", city_path(:name => #city.name) %>
And that is giving me this error:
No route matches {:action=>"destroy", :name=>"Los Angeles", :controller=>"cities"}
Finally, you should note that I plan on nesting resources underneath the City in the routes so that I end up with urls like http://myapp.com/los-angeles/messages/message-content-using-to-param
etc..
Any advice you could give would be greatly appreciated!
I personally use Friendly Id to handle pretty URLs with hooks into ActiveRecord(really nice by the way). This gem has nested resource support as well.
The method described here would be one approach. Overriding to_param is also described in the Rails docs.
Add this to your City model.
def to_param
"#{self.name_for_url}"
end
def name_for_url
self.name.gsub(/^A-Z0-9\-_/i,'')
end
This will cause city_path(#city) to provide /cities/los-angeles
If you want City to be your root controller, in routes.rb:
map.root :controller => 'city'
Now, http://myapp.com/los-angeles should work.

Reducing redundancy in Rails routes and url helper

I want to add article's title to its url similarly to SO URLs. I was suggested to use the following setup in answer to my another question
# routes.rb
match '/articles/:id/:title' => 'articles#show', :as => :article_with_title
# articles/index.html.erb
link_to article.title, article_with_title_path(article, :title => article.title.downcase.gsub(/[^a-z0-9]+/,' ').strip.gsub(/\s+/,'-'))
It works, however I find it a bit redundant. Is there a way to make it nicer? What about an additional universal method to handle multiple routes?
match '/articles/:id/:title' => 'articles#show'
match '/users/:id/:name' => 'users#show'
etc.
Remarks:
Currently the following routes work fine: /article/:id/:action, /article/:id/:title with a condition that article cannot have titles edit, show, index, etc.
I believe friendly_id is unnecessary here, since the routes contain :id explicitly.
As I see, SO uses different routes for questions /question/:id/:title, /posts/:id/:action and for users /users/:id/:name, /users/:action/:id
Just override to_param in your models. Untested example from memory:
def to_param
self.id + "-" + self.name.parameterize
end
this approach means you don't have to change the router, and can also keep using Model.find(params[:id]) or similar.
Basically what the Railscast mentioned in another answer does, and the core of what friendly_id does too.
Ryan Bates did an excellent screencast on using the model's name, or any other attribute, in the url instead of the id.

renaming routes (map, link_to, to_param) in rails

I'm having a little issue...I setup a rails application that is to serve a german website. To make use of Rails' internal pluralization features, I kept all my models in english (e.g. the model "JobDescription").
Now, if I call "http://mysite.com/job_descriptions/", I get all my job_descriptions....so far, so good. Because I didn't want the english term "job_descriptions" in my url, I put the following into my routes.rb
map.german_term '/german_term', :controller => 'job_descriptions', :action => 'index'
map.german_term '/german_term/:id', :controller => 'job_descriptions', :action => 'show'
If I call "http://mysite.com/german_term/" or "http://mysite.com/german_term/283" I get all my job_descriptions, which is fine.
However, to make the URL more SEO friendly, I'd like to exchange the id for a more userfriendly slug in the URL. Thus, I put the following in my job_description.rb:
def to_param
"#{id}-#{name.gsub(/[^a-z0-9]+/i, '-')}"
end
which, whenever I use "job_description_path" in any link_to method, renders my URLs out to something like "http://mysite/job_descriptions/13-my-job-description-title".
However, and this is where I'm stuck, I'd like to get "http://mysite/german_term/13-my-job-description-title". I already tried to exchange the "job_description_path" with "german_term_path" in the link_to code, but that only generates "http://mysite/german_term/13". Obviously, to_param isn't called.
One workaround I found is to build the link with:
<%= link_to job_description.name, german_term_path(job_description.to_param) %>
But that's rather tedious to change all the link_to calls in my code. What I want is to replace "job_description" by "german_term" whenever it occurs in a URL.
Any thoughts?!?
Regards,
Sebastian
I think you're going to need to use the restful route helpers to get what you want.
In that case, it wouldn't take much re-factoring (assuming you've mapped JobDescriptions as a resource). Leave your to_param as is and change your JobDescriptions route to something like the following:
map.resources :job_descriptions, :as => 'german_term'
Hope this helps!
Rails only utilizes the
def to_params
end
URL builder when you are using a restful route/link helper. The only way I am aware of is to do it similar to how you did, unless you are willing to just scrap your english language links and do it all in German. In that case, just get rid of the named route lines and change the to_params to use the correct name field from the database. At that point, the REST routes should behave correctly.

Resources