Reducing redundancy in Rails routes and url helper - ruby-on-rails

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.

Related

how to change links to more SEO friendly rails

I have such links in my app
http://localhost:3000/lv/manufacturer_products?manufacturer=Komptech
http://localhost:3000/en/products?category=Shredders
But my friend said that these links are not SEO friendly, tht I have to change them, to
http://localhost:3000/en/manufacturer_products/Komptech
or similair to this
http://localhost:3000/en/products/category/Shredders
But how can I actually change the structure off link without help off any gem ? using routes ?
Thanx
See documentation for namespaces and also this answer on SO.
You could even just do named routes. something like this:
resources :products do
resources :manufacturers
end
which for the index action of manufacturers would return this:
product_manufacturers GET /products/:product_id/manufacturers(.:format) manufacturers#index
and you could then write in routes.rb
match '/:id/products/:name',
:to => 'manufacturers#index', :as => :manufacturers
and when you call it
<%= link_to #manufacturer.name, manufacturers_path({id: #manufacturer.product_id, name: #manufacturer.name}) %>
which would render http://localhost:3000/x/products/Komptech
There is a railscast by Ryan Bates for this and I always follow this,
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid
I can not restrict me to share this ...Once I got an excellent help to optimize the SEO of my site
look into the link
http://complitech.net/seo-basics-high-benifit-for-ruby-on-rails-developer/
Look at the 3rd point got your answer for url
3) Improve your structure of URL
Generally in old fashion the url are unstructured and not directory wise , so make your URL are structured.
example:
www.herrybaseballcards.com/images/baseball/top-ten-baseballcards.html
so in routes
match '/:foldername/:products/:name',
:to => 'products#index', :as => :products
so ignore the Query Based URL Structures

Rails 3 Finding the right :id in a controller using a specific route

I have my routes arranged so that when visiting the site the :id is displayed before the slug like so
match "/causes/:id/:slug" => "causes#show", :as => :cause, :via => 'get'
But I also have a nested attribute called "post" that belongs to causes like so
match "/causes/:id/:slug/posts" => "causes#posts", :via => 'get', :as => :posts
When I use this, everything works great for the causes, but not for the posts.
If I use
#post = Post.find(params[:id])
in causes or posts controller it always looks for the ID of the causes, and not the :id of the posts. So if the post :id is 9, and the cause :id is 1, and I use
#post = Post.find(params[:id])
it will always look for post[1] and not 9 or whatever the post id really is.
What am I doing wrong? Is there a way to make this work in the routes, or maybe a different way to find the id of a nested object in the controller?
I need the route to be the way I have it set up, :id/:slug...
rake routes information:
cause GET /causes/:id/:slug(.:format) causes#show
edit_cause GET /causes/:id/:slug/edit(.:format) causes#edit
PUT /causes/:id/:slug(.:format) causes#update
posts GET /causes/:id/:slug/posts(.:format) causes#posts
POST /causes/:id/:slug/posts(.:format)
PUT /causes/:id/:slug/posts(.:format) causes#update_post
DELETE /causes/:id/:slug/posts(.:format) causes#destroy_post
causes GET /causes(.:format) causes#index
POST /causes(.:format) causes#create
Any help would be great.
To solve your immediate problem, you'll want to add something like this to routes.rb
# config/routes.rb
match "/causes/:cause_id/:slug/post/:id" => "causes#update_post", :via => 'put', :as => :update_post
And then you can generate the URL in your views like this...
link_to 'Update this post', update_post_path(#cause, #post)
...and access the parameters in your controller as params[:id] (for the post) and params[:cause_id] (for the cause).
More generally, though, the way you are specifying your routes is pretty cumbersome, and I suspect you're making your life harder than it needs to be. If this were me, I would do something like
# config/routes.rb
resources :causes do
resources :posts
end
This would accomplish something pretty close to what you have now, the main difference being that it wouldn't contain slugs. I'm not sure why you need to have both slugs and IDs, maybe you could just identify your causes by their slugs? Stringex is a good gem for generating slugs, and you can set it so that slugs are guaranteed to be unique.
Here is the section of the Rails guide on nested resources
http://guides.rubyonrails.org/routing.html#nested-resources
And here is a Railscast about using slugs with nested resources
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid?view=comments
Hope this helps.
This is because you're using the id of the cause, and if you're doing /causes/:id/posts shouldn't you be doing #posts = #cause.postsanyway?
I would look into the new router syntax for rails 3 if I were you, as there is a nicer way to nest resources http://guides.rubyonrails.org/routing.html
edit:
use the friendly_id gem and nest your resources, to avoid confusion follow REST best practises that resource in question is at the end so
/causes/:slug/posts/:slug

Correct method for custom Rails routing

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'

Rails "pretty URLs", using entries/23 or 2011/07/some-post-slug-here for creating URLs via helpers

I'm attempting to create "pretty URLs" for linking to posts in a blog. I want to maintain access to the blog entries via entries/23 and 2011/07/some-post-slug-here as I only generate a slug once an entry has been published (just in case the title of the posts changes, and, though not a strict requirement, I would prefer to be able to edit/delete posts via the entries/23 style URL. Currently, the appropriate part of what I have in my config/routes.rb:
root :to => 'entries#index'
resources :entries
match ':year/:month/:slug' => 'entries#show', :constraints => {
:year => /[0-9][0-9][0-9][0-9]/,
:month => /[0-9][0-9]/,
:slug => /[a-zA-Z0-9\-]+/
}, :as => :vanity_entry
and I use this (in my application helper) function for creating the links:
def appropriate_entry_path entry
if entry.published
vanity_entry_path entry.published_on.year.to_s, entry.published_on.month.to_s, entry.slug
else
entries_path entry
end
end
def appropriate_entry_url entry
if entry.published
vanity_entry_url entry.published_on.year.to_s, entry.published_on.month.to_s, entry.slug
else
entries_url entry
end
end
Basically, I check if the article is published (and therefore has a slug) and then use that URL/path helper, or otherwise use the default one.
However, when trying to use this, I get the following from Rails:
No route matches {:slug=>"try-this-one-on-for", :month=>"7", :controller=>"entries", :year=>"2011", :action=>"show"}
I have tried a few different solutions, including overriding to_param in my Entry model, however then I would have to create match routes for the edit/delete actions, and I would like to keep my routes.rb file as clean as possible. Ideally, I would also love to lose the appropriate_entry_path/appropriate_entry_url helper methods, but I'm not sure that this is possible?
Is there any thing I am missing regarding routing that might make this easier and/or is there any specific way of doing this that is the cleanest?
Thanks in advance.
You might want to take a look at friendly_id. It's a gem for creating seo friendly slugs :)
I found the issue with what I had been doing, the regex for :month in the route wanted two numbers, whereas I was only passing in one number. Anyways, I decided that the URLs look nicer (in my opinion) without the month padded to 2 digits, so I updated my route accordingly.

Rails RESTful resources using to_param for a field that contains separator characters

I want my Rails 2.3.2 app to respond to and generate URLs like so:
/websites/asd.com
/websites/asd.com/dns_records/new
In my config/routes.rb, I have:
map.resources :websites, :has_many => :dns_records
map.resources :dns_records, :belongs_to => :website
I can then access resources such as:
/websites/1
/websites/1/dns_records
By modifying my Website model, I can generate better URLs like so:
class Website < ActiveRecord::Base
def to_param
domain_name
end
...
end
# app/views/websites/index.erb
<% #websites.each do |w| %>
<%= link_to "Show #{w}", website_path(w) %>
<% end %>
# Produces a link to:
/websites/example_without_periods_in_name
However, for domain names that contain '.' characters, Rails gets unhappy. I believe this is because the '.' character is defined in ActionController::Routing::SEPARATORS, which lists special characters to split the URL on. This allows you to do stuff like /websites/1.xml.
SO, is there a clean way to allow '.' characters in RESTful URLs?
I've tried redefining ActionController::Routing::SEPARATORS to not include '.', which is a totally bad way to solve the problem. This messes up generated URLs by appending ".:format" to them.
I also know I can add :requirements => { :id => regexp } to my config/routes.rb to match a domain name that includes '.' (without this, params[:id] is set to the part of the domain name before the first '.'), but this doesn't help in generating URLs/paths RESTfully.
Many thanks :)
Nick
Solved the problem, with a big thanks to http://poocs.net/2007/11/14/special-characters-and-nested-routes (and see http://dev.rubyonrails.org/ticket/6426 for additional reference)
I needed to add :requirements => { :website_id => regexp } for each nested route that was also going to include a domain name with periods in it.
Here's my working route:
map.resources :websites, :requirements => { :id => /[a-zA-Z0-9\-\.]+/ } do |websites|
websites.with_options :requirements => { :website_id => /[a-zA-Z0-9\-\.]+/ } do |websites_requirements|
websites_requirements.resources :dns_records
end
end
<%= link_to 'New DNS Record', new_website_dns_record_path(#website) %>
# Produces the URL
/websites/asd.com/dns_records/new
The call to
websites.with_options
is simply in keeping with DRY so that :requirements don't have to be specified for all nested routes for websites. So I could also have
websites_requirements.resources :accounts
websites_requirements.resources :monthly_bandwidth_records
etc.
This is an interesting issue. I don't think you can get rid of the bad '.:format' appended to the end if you do a basic map.resources. If you want periods in the names, you are not in accordance with the usual rails styles, and I think a custom route might be in order if you absolutely NEED the '.' in the URL.
However, maybe you should consider changing your definition of to_param. What would you think about using the following?
def to_param
domain_name.sub('.','_dot_')
end
I think, if you're using this to manage customer websites, it's a fairly elegant way to generate nice (and SEO friendly) URLs like
/websites/asd_dot_com/dns_records/new
/websites/asd_dot_com/
I had a similar issue some time ago and came to a similar solution. Using /.+/ as the requirement for the param in question worked fine for me.
http://zargony.com/2009/05/05/routing-parameters-with-a-dot

Resources