Ruby on Rails: How to override the 'show' route of a resource? - ruby-on-rails

Currently I have a route that looks like this:
resources :posts
I want to override the 'show' action so that I can display a url like this:
posts/:id/:slug
I am currently able to do this by adding a custom match route:
resources :posts
match 'posts/:id/:slug' => 'posts#show'
However, when I use the link_to helper, it does not use my custom show route.
<%= link_to 'show', post %> # renders /posts/123
How can I define my show route so that I can still use the link_to helper?
Update: As you can read in the following answers, you can override the route to the 'show' action, but it's probably more work than it's worth. It's easier to just create a custom route:
# config/routes.rb
match 'posts/:id/:slug' => 'posts#show', as: 'post_seo'
# app/views/posts/index.html.erb
<%= link_to post.title, post_seo_path(post.id, post.slug) %>

You have two routes which point to posts#show (you should be able to confirm this by running rake routes), and your link is using the wrong one.
When you call link_to('show', post) the URL of the link is generated by calling url_for(post) which (eventually, after passing through several other methods on the way) calls post_path(post). Since the route to posts#show that was created by your call to resources(:posts) is named post, that is the route that post_path generates.
You also currently have inconsistent routes for the show, update and destroy actions which will probably cause you problems later on.
You can fix this by changing your routes to the following:
resources :posts, :except => ['show', 'update', 'destroy']
get 'posts/:id/:slug' => 'posts#show', :as => 'post'
put 'posts/:id/:slug' => 'posts#update'
delete 'posts/:id/:slug' => 'posts#destroy'
Unfortunately you still can't use link_to('show', post) just yet, because it relies on being able to use post.to_param as the single argument needed to build a path to a post. Your custom route requires two arguments, an id and a slug. So now your link code will need to look like this:
link_to 'show', post_path(post.id, post.slug)
You can get around that problem by defining your own post_path and post_url helpers in app/helpers/posts_helper.rb:
module PostsHelper
def post_path(post, options={})
post_url(post, options.merge(:only_path => true))
end
def post_url(post, options={})
url_for(options.merge(:controller => 'posts', :action => 'show',
:id => post.id, :slug => post.slug))
end
end
Which means we're finally able to use:
link_to 'show', post
If that all seems like too much work, a common alternative is to use URLs that look more like posts/:id-:slug, in which case you can stick with the standard RESTful routes and just override the to_param method in your Post class:
def to_param
"#{id}-#{slug}"
end
You'll also need to do a little bit of work splitting up params[:id] into an ID and a slug before you can look up the relevant instance in your show, edit, update and destroy controller actions.

resources :posts, except: :show do
get ":slug" => :show, as: "", on: :member
end
and define helper
def post_path post
"/posts/#{post.id}/#{post.slug}"
end

db/migrate/add_slug_to_articles.rb
add_column :articles, :slug, :string
add_index :articles, :slug
models/article.rb
class Article < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
def should_generate_new_friendly_id?
new_record?
end
end
Or...
class Article < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :history
end
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid
https://github.com/norman/friendly_id

Related

rails remove controller path from the url

I have the following loop in my view
<% #posts.each do |post| %>
<%= link_to post do %>
Some html
<% end %>
<% end %>
The above code will generate link as localhost:3000/posts/sdfsdf-sdfsdf
But I would like to have the link as localhost:3000/sdfsdf-sdfsdf
Here is my route
resources :posts, except: [:show]
scope '/' do
match ':id', to: 'posts#show', via: :get
end
You could do this:
#config/routes.rb
resources :posts, path: "" #-> domain.com/this-path-goes-to-posts-show
--
Also, make sure you put this at the bottom of your routes; as it will override any preceding routes. For example, domain.com/users will redirect to the posts path unless the posts path is defined at the bottom of the routes.rb file
--
friendly_id
In order to achieve a slug-based routing system (which works), you'll be best suited to using friendly_id. This allows the .find method to look up slug as well as id for extended models:
#app/models/post.rb
Class Post < ActiveRecord::Base
extend FriendlyID
friendly_id :title, use: [:slugged, :finders]
end
This will allow you to use the following in your controller:
#app/controllers/posts_controller.rb
Class PostsController < ApplicationController
def show
#post = Post.find params[:id] #-> this can be either ID or slug
end
end
you need to tell routes what the name of the path gonna be.
in routes.rb you can do something like:
get '/:id', constraints: { the_id: /[a-z0-9]{6}\-[a-z0-9]{6}/ }, to: 'posts#show', as: :custom_name
after that when you run 'rake routes' you will see:
Prefix Verb URI Pattern Controller#Action
custom_name GET /:id(.:format) post#show {:id=>/[a-z0-9]{6}\-[a-z0-9]{6}/}
Now that you have the prefix verb, you can use it to generate the link:
<%= link_to 'Show', custom_name_path( post.id ) do %>
Some html
<% end %>

Routes with optional parameters

Following attempt seems to be functional, but is not the 'clean' result that I am trying to archive.
Following route
get "/learn(/:category)", to: "users#index", as: "learn"
Should be useable for something like "/learn/technology" - Which works, if entered manually in the address bar.
If I tough try to achieve similar in my views, I get the following: "/learn?category=technology" - Which well, technically works, but is not what I want.
I'm using the following inside my view:
- Category.promoted.limit(7).each do |category|
%li.category-button
= link_to learn_path(category) do
= button_tag "", :class => "#{category.name}"
= content_tag(:span, category.to_s, :class => 'category-head')
And my Category Model looks the following:
class Category < ActiveRecord::Base
has_many :skills
validates_uniqueness_of :name
scope :promoted, lambda { where(:promoted => true) }
def to_s
read_attribute(:name).titleize
end
def to_param
name.parameterize
end
end
How would I achieve the 'cleaner' solution?
Edit:
Following works - but there must be a better solution than that?
get "/learn", to: "users#index", as: "learn"
get "/learn/:category", to: "users#index", as: "filter_learn"
Try changing your link to the following:
...
= link_to learn_path(category: category.name) do
...
You may use url_for to solve the problem.
Suppose I have UsersController with index action and this in routes.rb:
resources :users, only: [:index] do
collection do
get ':kind', :to => 'users#index'
end
end
Then when I'm on /users page I can use the url_for this way:
= link_to 'Kind1', url_for(kind: :students)
which will produce path:
/users/students
If I'm on some another page (another controller or another action), then I shoud provide more info. For example when I'm on another controller's page then I should provide both controller and action params if target action is not index (if target action is index then it is sufficient to provide only controller):
= link_to 'Kind1', url_for(controller: :users, action: :index, kind: :students)
it produces the same path:
/users/students
While using users_path(kind: :students) you'll get:
/users?kind=students

How to add a custom action to the controller in Rails 3

I want to add another action to my controller, and I can't figure out how.
I found this on RailsCasts, and on most StackOverflow topics:
# routes.rb
resources :items, :collection => {:schedule => :post, :save_scheduling => :put}
# items_controller.rb
...
def schedule
end
def save_scheduling
end
# items index view:
<%= link_to 'Schedule', schedule_item_path(item) %>
But it gives me the error:
undefined method `schedule_item_path' for #<#<Class:0x6287b50>:0x62730c0>
Not sure where I should go from here.
A nicer way to write
resources :items, :collection => {:schedule => :post, :save_scheduling => :put}
is
resources :items do
collection do
post :schedule
put :save_scheduling
end
end
This is going to create URLs like
/items/schedule
/items/save_scheduling
Because you're passing an item into your schedule_... route method, you likely want member routes instead of collection routes.
resources :items do
member do
post :schedule
put :save_scheduling
end
end
This is going to create URLs like
/items/:id/schedule
/items/:id/save_scheduling
Now a route method schedule_item_path accepting an Item instance will be available. The final issue is, your link_to as it stands is going to generate a GET request, not a POST request as your route requires. You need to specify this as a :method option.
link_to("Title here", schedule_item_path(item), method: :post, ...)
Recommended Reading: http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
Ref Rails Routing from the Outside In
Following should work
resources :items do
collection do
post 'schedule'
put 'save_scheduling'
end
end
You can write routes.rb like this:
match "items/schedule" => "items#schedule", :via => :post, :as => :schedule_item
match "items/save_scheduling" => "items#save_scheduling", :via => :put, :as => :save_scheduling_item
And the link_to helper can not send post verb in Rails 3.
You can see the Rails Routing from the Outside In

Add new action to route

I got these actions in users controller
class UsersController < ApplicationController
def index #default action
...
end
def new #default action
...
end
def another_new
...
end
def create
...
end
def another_create
...
end
end
I want to be able to
/users/another_new and call from some sort of link :method => :another_create
to make /users/another_new
I got the following config/routes.rb
get '/users/another_new' :to => 'users#another_new'
resources :users
my question is if this is the correct way to add the get and how I add the another_create method.
in your config/routes.rb file do this
resources :users do
collection do
get 'another_new'
post 'another_create'
end
end
Also have a look HERE for clear understanding of concepts.
Hope this helps you dude :)
try this in routes.rb
match "/users/another_new " => "users#another_new", :as => 'another_new'
then you can do
link_to "MyUrl", another_new_path
this should work. Good luck.
Also note that you shouldn't have :method => :another_new. Your options for :method are :get, :put, :post, and :delete, and the one you use should match how you defined the action in routes.

Rails routing help

I want the url of a user post to be like this:
example.com/users/username/post-name
How should I set this up?
Currently, the post model contains:
def to_param
name.parameterize
end
(to hyphenate the post name accordingly)
routes.rb contains:
map.resources :users
map.resources :posts
Preferably, I would like to say post_path(post) and this would generate the appropriate path for me by finding the post.user automatically.
What do I need to do to make this happen?
Also added
map.resources :users do |user|
user.resources :posts
end
One way more:
map.resources :users do |user|
user.connect ':name/:action', :controller => 'posts', :defaults => {:action => 'show'}
end
Available routes:
example.com/users/username/post-name
example.com/users/username/post-name/edit (any action)
Hi To make your application recognize routes like
example.com/users/username/post-name
you should add to your routes.rb
map.connect 'users/:username/:post', :controller => "users", :action => "test"
Then you can access params[:username] and params[:post] inside your controllers test action You should define it after map.resources :users but before map ':controller/:action/:id' but you must write your own helper then
That to_param looks okay, but the builtin helpers don't link to nested resources like you want, you'd have to use user_post_path(user, post). Ofcourse, you could write your own post_path helper method which does work the way you want.
def post_path(post)
url_for [post.user, post]
end

Resources