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.
Related
I am trying to create short url links for books in Ruby on Rails. I want to get something like this: www.domain.com/book123, where book is the controller name (or custom controller name) and 123 is an id of the book.
Right now my routes look as follow:
resources :books, except: [:edit], path: "book" do
put :new, on: :new
member do
get ':id' => 'books#show'
get 'general' => 'books#general'
get 'additional' => 'books#additional'
get 'photos' => 'books#photos'
get 'map' => 'books#map'
end
resources :photos, only: [:create, :destroy]
end
This is what I get: http://localhost:3000/book/40 or www.domain.com/book/40.
I was trying to find similar questions and I found that the only way to achieve this is to use regex. I am new in Ruby on Rails and I want to find the right and efficient way of doing it.
Also, I might be wrong but I've noticed that some of the urls can affect on the website performance, so I don't want to have such problems.
Any help, information or examples will be highly appreciated. Thank you for your help and time.
You could try this route:
get 'book*id' => 'bookscontroller#show'
Check this article: https://www.railsmine.net/2014/10/route-globbing-in-ruby-on-rails.html
As #qdx47 has mentioned you'd better follow convention, but if you must not, I think you can override to_param on book model, like:
def to_param
"book#{id}"
end
and then define routes like
get ':id', to: 'books#show', constraints => { :book_id => /book[0-9]+/ }
I think you can give a try to below gem.
Friendly Id Gem
Then you will be able to generate slug that can be any unique string. By default it will be uuid but you can override it. Follow gem documentation. It will allow you generate routes like http://localhost:3000/books/book123.
In general, I think you would be going against convention and best practices by formatting your route in this way.
With that caveat, you should be able to define a route like so:
get(':book_id', 'books#show', constraints => { :book_id => /book[0-9]+/ })
You would then need to extract the id from the 'book' literal in the controller.
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'
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.
Is this possible?
def to_param
"#{id}%2F#{slug}"
end
This works in Chrome and Safari, but if Firefox you see the "%2F" in the address bar. Is there a cleaner way?
This is indeed an old post, but I want to build a bit on it.
If you don't want to have to handle an slug variable in your params, you really need to define that method to_param in your model
def to_param
"#{id}/#{title}"
end
and set a route like so:
resources :posts, :id => /[0-9]+\/.+/
That way your link definition looks quite like a normal one:
link_to post.title, post_url(post)
Very simple: http://www.miguelsanmiguel.com/2011/03/17/slug-that-slash
Ok I tried this just now you won't need this:
def to_param
"#{id}/#{slug}"
end
In routes.rb (replace what you want with what you need)
match "/expenses/:id/:slug" => "expenses#show", :as => :expense
Your link_to should look like this now
= link_to "something", expense_url(:id => expense.id, :slug => expense.slug)
hope this helps a bit
Have a look at Friendly ID -- it will eliminate the ID entirely and just use the slug. It is Rails 3 compatible as well.
maybe something like this can help you
match "/foo/:id", :to => redirect("/bar/%{id}s")
check out the section The "Redirect Method" in this article about rails3 and routes
http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/
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