Advanced Routing with Rails3 - ruby-on-rails

I want to use regular expressions inside my routes. I have an Products controller, but I want a different URL structure to access the products
http://host/this/
http://host/that/
http://host/andthat/
These URLs should call a action in my controller (Products:show_category(:category))
Is something like this possible?
match "(this|that|andthat)" => "products#show_category", :category => $1
the action should look like this
def show_category
puts params[:category] # <-- "this" if http://host/this/ is called
# ...
end

I haven't actually tested it, but try out:
match ':category' => 'products#show_category', :constraints => { :category => /this|that|andthat/ }

I'm not too sure if this answers your question, but you could add a collection to routes.rb:
resources :products do
collection do
get :category1
get :category2
get :category3
end
end
If you then run rake routes, you'll see that you have urls like /products/category1 and products/category2. Category1, 2 and 3 can be defined in your controller as usual:
def category1
#custom code here
end
def category2
#custom code here
end
def category3
#custom code here
end
As I said, I'm not too sure if that's what you're looking to do, but hope that helps a bit!

Related

Rails nested routes for simple blog

I need to make page for my blog and also for each post under blog. It should look like this example.com/blog/my_first_post. Posts are static HTML files and I don't use any database.
Here is my routes:
get 'blog' => 'static_pages#blog' do
get '/my_first_post' => 'blog#my_first_post'
end
Here is my StaticPages controller:
...
def blog
def my_first_post
end
end
...
Blog page is working fine, but post is not working.
To get this example.com/blog/my_first_post, your routes should look like the followings -
get 'blog' => 'static_pages#blog'
get 'blog/my_first_post' => 'static_pages#my_first_post'
Your controller should be looking like this-
class StaticPagesController < ApplicationController
def my_first_post
end
def blog
end
end
Check this one: but one thing
If its just static page then better use as like in below.
get 'blog/my_first_post' => 'static_pages#my_first_post'
you cannot call that method like view if its in it. make it like this.
def blog
end
def my_first_post
end
Use below, above resources: blog if present
get 'blog/my_first_post'
Have a try:
get 'blog/my_first_post', to: Proc.new { |env|
[
200,
{"Content-Type" => "text/html"},
[File.read("public/my_first_post.html")] // where you static files are
]
}
In your case, a controller is redundant

Rails routes remove characters from URL

I have URLs like this
arizona/AZ12
colorado/CO470
I added the AZ and CO because friendly id wanted unique ids. Arizona and Colorado could have a unit 12.
I'd like to have URLs like
arizona/unit12
colorado/unit470
Seems like you could write something that removes the first two characters and replaces them. Would that be in the routes or controller?
My routes
resources :states, :except => [:index ], :path => '/' do
resources :units, :except => [:index ], :path => '/'
end
My controller
def show
#units = Unit.all
#states = State.with_units.group('states.id')
#state = State.all
#unit = Unit.friendly.find(params[:id])
end
Implement to_param method on your model. Rails will call to_param to convert the object to a slug for the URL. If your model does not define this method then it will use the implementation in ActiveRecord::Base which just returns the id.
class SomeModel
def to_param
"unit#{id}"
end
end
You can refer https://gist.github.com/agnellvj/1209733 for example

Custom parameters in URL for show action

I'm working on implementing a SEO-hiarchy, which means that I need to prepend parameters for a show action.
The use-case is a search site where the URL-structure is:
/cars/(:brand)/ => a list page
/cars/(:brand)/(:model_name)?s=query_params => a search action
/cars/:brand/:model_name/:variant/:id => a car show action
My problem is to make the show action URLs work without having to provide :brand, :model_name and :variant as individual arguments. They are always available from as values on the resource.
What I have:
/cars/19330-Audi-A4-3.0-TDI
What I want
/cars/Audi/A4/3.0-TDI/19330
Previously, this was how the routes.rb looked like:
# Before
resources :cars. only: [:show] do
member do
get 'favourize'
get 'unfavourize'
end
Following was my first attempt:
# First attempt
scope '/cars/:brand/:model_name/:variant' do
match ":id" => 'cars_controller#show'
match ":car_id/favourize" => 'cars_controller#favourize', as: :favourize_car
match ":car_id/unfavourize" => 'cars_controller#unfavourize', as: :unfavourize_car
end
This makes it possible to do:
cars_path(car, brand: car.brand, model_name: car.model_name, variant: car.variant)
But that is obviously not really ideal.
How is it possible to setup the routes (and perhaps the .to_param method?) in a way that doesn't make it a tedious task to change all link_to calls?
Thanks in advance!
-- UPDATE --
With #tharrisson's suggestion, this is what I tried:
# routes.rb
match '/:brand/:model_name/:variant/:id' => 'cars#show', as: :car
# car.rb
def to_param
# Replace all non-alphanumeric chars with - , then merge adjacent dashes into one
"#{brand}/#{model_name}/#{variant.downcase.gsub(/[^[:alnum:]]/,'-').gsub(/-{2,}/,'-')}/#{id}"
end
The route works fine, e.g. /cars/Audi/A4/3.0-TDI/19930 displays the correct page. Generating the link with to_param, however, doesn't work. Example:
link_to "car link", car_path(#car)
#=> ActionView::Template::Error (No route matches {:controller=>"cars", :action=>"show", :locale=>"da", :brand=>#<Car id: 487143, (...)>})
link_to "car link 2", car_path(#car, brand: "Audi")
#=> ActionView::Template::Error (No route matches {:controller=>"cars", :action=>"show", :locale=>"da", :brand=>"Audi", :model_name=>#<Car id: 487143, (...)>})
Rails doesn't seem to know how to translate the to_param into a valid link.
I do not see any way to do this with Rails without tweaking either the URL recognition or the URL generation.
With your first attempt, you got the URL recognition working but not the generation. The solution I can see to make the generation working would be to override the car_path helper method.
Another solution could be, like you did in the UPDATE, to override the to_param method of Car. Notice that your problem is not in the to_param method but in the route definition : you need to give :brand,:model_name and :variant parameters when you want to generate the route. To deal with that, you may want to use a Wildcard segment in your route.
Finally you can also use the routing-filter gem which make you able to add logic before and after the url recognition / generation.
For me, it looks like all theses solutions are a bit heavy and not as easy as it should be but I believe this came from your need as you want to add some levels in the URL without strictly following the rails behavior which will give you URL like /brands/audi/models/A3/variants/19930
OK, so here's what I've got. This works in my little test case. Obviously some fixups needed, and I am sure could be more concise and elegant, but my motto is: "make it work, make it pretty, make it fast" :-)
In routes.rb
controller :cars do
match 'cars', :to => "cars#index"
match 'cars/:brand', :to => "cars#list_brand", :as => :brand
match 'cars/:brand/:model', :to => "cars#list_model_name", :as => :model_name
match 'cars/:brand/:model/:variant', :to => "cars#list_variant", :as => :variant
end
In the Car model
def to_param
"#{brand}/#{model_name}/#{variant}"
end
And obviously fragile and non-DRY, in cars_controller.rb
def index
#cars = Car.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #cars }
end
end
def list_brand
#cars = Car.where("brand = ?", params[:brand])
respond_to do |format|
format.html { render :index }
end
end
def list_model_name
#cars = Car.where("brand = ? and model_name = ?", params[:brand], params[:model])
respond_to do |format|
format.html { render :index }
end
end
def list_variant
#cars = Car.where("brand = ? and model_name = ? and variant = ?", params[:brand], params[:model], params[:variant])
respond_to do |format|
format.html { render :index }
end
end
You just need to create two routes, one for recognition, one for generation.
Updated: use the routes in question.
# config/routes.rb
# this one is used for path generation
resources :cars, :only => [:index, :show] do
member do
get 'favourize'
get 'unfavourize'
end
end
# this one is used for path recognition
scope '/cars/:brand/:model_name/:variant' do
match ':id(/:action)' => 'cars#show', :via => :get
end
And customize to_param
# app/models/car.rb
require 'cgi'
class Car < ActiveRecord::Base
def to_param
parts = [brand,
model_name,
variant.downcase.gsub(/[^[:alnum:]]/,'-').gsub(/-{2,}/,'-'),
id]
parts.collect {|p| p.present? ? CGI.escape(p.to_s) : '-'}.join('/')
end
end
Sample of path helpers:
link_to 'Show', car_path(#car)
link_to 'Edit', edit_car_path(#car)
link_to 'Favourize', favourize_car_path(#car)
link_to 'Unfavourize', unfavourize_car_path(#car)
link_to 'Cars', cars_path
form_for(#car) # if resources :cars is not
# restricted to :index and :show
You want bounded parameters to be passed to url of which some parameters are optional and some of them strictly needs to be present.
Rails guides shows you can have strict as well as optional parameters and also you can give name to particular route in-order to simplify its usage.
Guide on rails routing
bound parameters
Example usage -
In below route,
brand is optional parameter as its surrounded by circular bracket
Also please note there can be optional parameters inside route but they needs to added at last /cars(/:brand)(/:make)(/:model)
match '/cars/(:brand)', :to => 'cars#index', :as => cars
here cars_url will map to index action of cars controller..
again cars_url("Totoya") will route index action of cars controller along-with params[:brand] as Toyota
Show url route can be as below where id is mandatory and others can be optional
match '/cars/:id(/:brand(/:model_name/)(/:variant)', :to => "cars#show", :as => car
In above case, id is mandatory field. Other parameters are optional.
so you can access it like car_url(car.id) or car_url(12, 'toyota') or car_url(12, 'toyota', 'fortuner') or car_url(12, 'toyota', 'fortuner', 'something else)

How to set up routes to show additional information in the URL using namespaces?

I am running Ruby on Rails 3 and I would like to set up my routes to show additional information in the URL using namespaces.
In the routes.rb file I have:
namespace "users" do
resources :account
end
So, the URL to show an account page is:
http://<site_name>/users/accounts/1
I would like to rewrite/redirect that URL as/to
http://<site_name>/user/1/Test_Username
where "Test_username" is the username of the user. Also, I would like to redirect all URLs like
# "Not_real_Test_username" is a bad entered username of the user.
http://<site_name>/users/accounts/1/Not_real_Test_username
to the above.
At this time I solved part of my issuelike this:
scope :module => "users" do
match 'user/:id' => "accounts#show"
end
My apologies for not answering your question (#zetetic has done that well enough), but the best practice here is to stay within the RESTful-style Rails URL scheme except for rare exceptions. The way most people make pretty URLs in this way is to use a hyphen, e.g.:
/accounts/1-username
This does not require any routing changes. Simply implement:
class Account < ActiveRecord::Base
def to_param
"#{self.id}-#{self.username}"
end
end
And handle the extra string data in your finds by calling to_i.
class AccountController < ApplicationController
def show
#account = Account.find(params[:id].to_i)
end
end
When you do link_to 'Your Account', account_path(#account), Rails will automatically produce the pretty URL.
It's probably best to do this in the controller, since you need to retrieve the account to get the username:
#account = Account.find(params[:id])
if #account && #account.username
redirect_to("/user/#{#account.id}/#{#account.username}")
return
end
As to the second issue, you can capture the remaining parameter by defining it in the route:
get "/users/accounts/:id(/:other)" => "users/accounts#show"
This maps like so:
/users/accounts/1/something # => {:id => "1", :other => "something"}
/users/accounts/1 # => {:id => "1"}
And you can simply ignore the :other key in the controller.

How to add action 'current_user' to a restful 'user'?

I have a model 'User', it's a restful resource, and has the default methods like 'index, show, new, create' and others.
Now, I want to define a new action 'current_user', to show the information of current logged-in user, which is different from 'show'.
When I use:
link_to current_user.name, :controller=>'users', :action=>'current_user'
The generated url is http://localhost:3000/users/current_user, and error message is:
Couldn't find User with ID=current_user
Shall I have to modify the routes.rb? What should I do?
I have searched for some articles, and still have no idea.
Add
map.resources :users, :collection => {:current => :get}
Then, I use:
link_to 'current', current_users_path()
The generated url is:
http://localhost:3000/users/current
Now, everything is OK. Is this the best solution?
See my comment on the other answer for an explanation
map.current_user "users/current", :controller => :users, :action => :current
View:
link_to 'current', current_user_path
I would not add a new action for this. I would check the id passed to the show method.
class UsersController
def show
return show_current_user if params[:id] == "current"
# regular show code
end
private
def show_current_user
end
end
In the view use :current as the user id while generating path.
user_path(:current)

Resources