Routing HTTP verbs in Rails - ruby-on-rails

I'm trying to route only an http verb. Say I have a comments resource like so:
map.resources :comments
And would like to be able to destroy all comments by sending a DELETE /comments request. I.e. I want to be able to map just the http verb without the "action name" part of the route. Is this possible?
Cheers

You could do this:
map.resources :comments, :only => :destroy
which produces a route like the following (you can verify with rake routes)
DELETE /comments/:id(.:format) {:controller=>"comments", :action=>"destroy"}
But note that the RESTful destroy is designed for removing a specific record not all records so this route is still expecting an :id parameter. A hack might be to pass some sentinel value for :id representing "all" in your application context.
On the other hand, if your comments belong to another model, then removing the other model would/should remove the comments too. This is conventionally how multiple row deletes might normally occur.

Since this is not standard RESTful action, you will need to use a custom route.
map.connect '/comments',
:controller => 'comments',
:action => "destroy_all",
:conditions => { :method => :delete }
In your controller:
class CommentsController < ApplicationController
# your RESTful actions here
def destroy_all
# destroy all your comments here
end
end
In view, invoke like this:
<%= link_to "delete all comments",
comments_path,
:method => :delete,
:confirm => "Are you sure" %>
ps. I didn't test this code, but I think it should work.

Related

how can I specify controller and method in a link using haml with rails?

I am new in ralis, and I am trying to create a link in what calls an specific method of a specific controller, in this case car and method add_to_cart sending a parameters, unfortunately I dont know how to do it
For example, I want something this, but even if I scape characters
= link_to "Add to cart", {:controller => "car", :action => "add_to_cart", :car => car.id }
If I paste it just like that I get an error saying No route matches , any way to make it? or maybe in the old format (I dont understand it) I guess should be something like
=link_to "car", add_to_cart_car_path(#car.id), class: "btn"
Also shows me an error,
Any idea of this?
Assuming you have a route setup for the resource called car:
resources :cars
You can add a member action by changing it to:
resources :cars do
get :add_to_cart, :on => :member
end
You can now write:
= link_to "Add to Cart", add_to_cart_car_path(#car), class: 'btn'
You can add the route to routes.rb:
match 'add_to_cart' => 'cars#add_to_cart'
Remember to restart your rails server for the new route to be recognized.

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

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

Pretty URL in Rails when linking

Let's say I have a Ruby on Rails blogging application with a Post model. By default you would be able to read posts by http://.../post/id. I've added a route
map.connect ':title', :controller => 'posts', :action => 'show'
that will accept http://.../title (titles are unique) and the controller will do a query for the title and display the page. However when I am now calling <%= link_to h(post.title), post %> in a view, Rails still gives me links of the type post/id.
Is it possible to get Rails to automatically create the pretty links for me in this case?
If you are willing to accept: http:/.../1234-title-text you can just do:
def to_param
[id, title.parameterize].join("-")
end
AR::Base.find ignores the bit after the id, so it "just works".
To make the /title go away, try naming your route:
map.post ':id', :controller => 'posts', :action => 'show', :conditions => {:id => /[0-9]+-.*/ }
Ensure this route appears after any map.resources :posts call.
You can override ActiveRecord's to_param method and make it return the title. By doing so, you don't need to make its own route for it. Just remember to URL encode it.
What might be a better solution is to take a look at what The Ruby Toolbox has to offer when it comes to permalinks. I think using one of these will be better than to fixing it yourself via to_param.
I would use a permalink database column, a route, and I normally skip using link_to in favor of faster html anchor tags.
Setting your route like:
map.connect '/post/:permalink', :controller => 'post', :action => 'show'
then in posts_controller's show:
link = params[:permalink]
#post = Post.find_by_permalink(link)
You link would then be
Link
then in your create method, before save, for generating the permalink
#post = Post.new(params[:post])
#post.permalink = #post.subject.parameterize
if #post.save
#ect
There is a Gem for you to get this done perfectly
https://github.com/rsl/stringex

Find HTML verb for actions in rails

Is there a way to look up the HTML for a given controller action? For example, I would like to be able to associate GET with index and PUT with update. I want to be able to do this dynamically based on the routes.
I can get the action methods for each controller using Controller.action_methods, but this returns a set of strings of action methods. Ideally what I would like is a hash of the form: {:action => :verb}.
Read the rake routes task, that will provide insight:
e.g:
users GET /users(.:format) {:controller=>"users", :action=>"index"}
I assume this is what you are after?
:method is part of a :conditions hash you can pass in to map.connect
map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
:conditions => { :method => :get }
To provide a useful object with all controllers, actions, and associated verbs
def all_routes
#all_routes ||= Rails.application.routes.routes.map do |route|
{ alias: route.name,
path: route.path.spec.to_s,
controller: route.defaults[:controller],
action: route.defaults[:action],
verb: route.verb.source.to_s.delete('$'+'^')
}
end
end

Ruby on Rails: Using a "complete_tasks_controller" for RESTful Rails

I'm having troubling completing a task the RESTful way. I have a "tasks" controller, and also a "complete_tasks" controller.
I have this in the complete_tasks_controller create action:
def create
#task = Task.find(params[:id])
#task.completed_at = Time.now
#task.save
end
I tried calling this:
<%=link_to "Complete task", new_task_complete_task_path(#task), :method => :post %>
..but I'm getting errors on that mentioning that "Only get, put, and delete requests are allowed."
Do you know what I'm doing wrong?
It would make more sense to move this into an action called complete in your controller:
def complete
#task = Task.find(params[:id])
#task.complete!
end
To access this action using RESTful routing you'll need to define a new member route like this in config/routes.rb:
map.resources :tasks, :member => { :complete => :put }
Adding :member => { :complete => :put } to the end of any pre-existing map.resources :tasks will do the trick also, you should only ever have one map.resources :tasks line, unless it's nested. The routing guide explains this better than I ever could.
To get to it from the view:
link_to "Complete this task", complete_task_path(#task), :method => :put
The method complete! would then be defined in your model like so:
def complete!
self.completed_at = Time.now
save!
end
The reason for this is that it puts the model logic where it belongs: in the model.
Each map.resources statement routes.rb creates a common RESTful routes for use with the specified resource. The appeal of REST is that is uses the request type and url to determine which action to take. Out of the four verbs associated with HTTP, each one has a specific use.
POST => Create
GET => Retrieve
PUT => Update
DELETE => Destroy
The reason you're getting an error about only get, put, and delete requests being allowed, is that you're using a post request. Essentially you're telling Rails you want to create a task with an id of one. However you cannot create an item that already exists. Which is why posts are not allowed. Instead you want to use put, because you're updating an existing record.
You can do it by changing post, to put in your link_to call.
<%=link_to "Complete task", new_task_complete_task_path(#task), :method => :put %>
Have a read through the routing guide and the resources documentation, it will help you understand the difference between HTTP requests, as well as provide some insight into how Rails handles those requests.

Resources