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.
Related
I'm using a custom action to get the id of a project into the session, so that only relevant info for that project is shown in other areas. I've made a custom action in the projects controller, and am having trouble getting a link to work in the view to call that action. I just get an error saying "Couldn't find project without ID". I'm new to rails - I know it's probably an easy question, but help would be much appreciated, thanks!
View Code:
<%= link_to 'Select Project', :action => :select_project %>
Controller Code:
def select_project
#project = Project.find(params[:id])
session[:project_id] = #project.id
end
Routes:
resources :projects do
collection do
get :select_project
end
end
Alternative routes code:
resources :projects do
put 'select_project', on: :member
end
This is untested but I believe it is what you are looking for:
Routes:
resources :projects do
member do
post :set_current
end
end
this should create the following:
Endpoint: /projects/:id/set_current POST
Helper: set_current_project_path
Controller
def set_current
project = Project.find(params[:id])
session[:project_id] = project.id
redirect_to projects_path, :notice => "Current project set to #{project.name}"
end
Views
# index / erb tags excluded for simplicity
#projects.each do |project|
link_to 'Select Project', set_current_project_path(project), :method => :post
end
# show
<%= link_to 'Select Project', set_current_project_path(#project), :method => :post %>
See:
http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
Note also the use of 'post' instead of 'get', since we are changing the state of an object (session)
it is preferred to use a post not a get, otherwise users might pull up an old get request in the address bar
of their browser and set their session to a project unknowingly.
like varatis said - use rake routes or CONTROLLER=projects rake routes to help with determining what your route/path helpers look like and what http verbs they are expecting
And is there a reason why it's project not #project in the controller
The #project creates an instance variable; in a rails controller instance variables are made available to the views. This set_current action will never render a view, so no reason to make an instance variable out of it.
How come you have to set it to member and not collection in the routes
any action where you want to reference params[:id] should be a member route, an alternative would be to leave it as a collection route and pass params[:project_id] and pass that in all of your link_to calls, but in this case member makes more sense.
I believe resources :projects is a short cut for this break down
member do
get :show
get :edit
put :update
delete :destroy
end
collection do
get :index
get :new
post :create
end
hopefully that clarifies your questions some?
I think the route generated would be select_project_projects_path.
Link:
<%= link_to 'Select Project', select_project_projects_path %>
For future reference, run rake routes to see the automatic route helpers generated by Rails.
So, I have the following link-to:
<%= link_to(outing_add_guests_path, :class => 'modal') do %>
<div id="notImportant"></div>
<% end %>
When I click on it, Rails tells me that
No route matches {:controller=>"outings", :action=>"add_guests"}
However, here's my routes file:
resources :outings do
get "/add_guests" => "outings#add_guests"
post "/add_guests" => "outings#add_guests"
delete "/remove_guests" => "outings#remove_guests"
end
and the corresponding action from my Outings Controller:
def add_guests
#outing_guest = OutingGuest.new(:outing_id => params[:outing_id])
#outing_guest.user_id = params[:user_id]
if #outing_guest.save
flash[:notice] = "Guest added successfully"
redirect_to({ :action => 'outing', :id => params[:outing_id] })
else
flash[:notice] = "Guest could not be added"
redirect_to({ :action => 'outing', :id => params[:outing_id] })
end
end
Is there any reason Rails would be unable to detect my controller or its actions?
EDIT: Here's part of the results from rake routes
outing_add_guests GET /outings/:outing_id/add_guests(.:format) outings#add_guests
POST /outings/:outing_id/add_guests(.:format) outings#add_guests
I notice your link_to is not consistent with the other routes
outing_add_guests_path
outings_add_guests_path
Did you do rake routes to verify that outing_add_guests_path exists?
EDIT:
Your rake routes shows you need an outing_id so your routes aren't setup right (at least not for the POST). I'd fix them the way #RyanBigg is suggesting.
You should be defining these routes using the collection method:
resources :outings do
collection do
get :add_guests
post :add_guests
delete :remove_guests
end
end
What this will do is define new routes for the specified actions, as well as automatically defining the routing helpers for those routes. For more information please read the Routing Guide.
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.
I am new to rails. My rails version is 2.3.5. I found usage like:
In controller, a destroy method is defined and in view, you can use :action => "delete" to fire that method. Isn't the action name has to be the same as the method name? Why delete is mapped to destroy?
Again, in my controller, I define a method called destroy to delete a record. In a view, I have <%= link_to "remove", :action => 'destroy', :id => myrecord %>. But it never works in practice. Every time I press the remove link, it redirects me to the show view, showing the record's content. I am pretty sure that my destroy method is:
def destroy
#myobject = MyObject.find(params[:id])
#myobject.destroy
#redirect_to :action = 'index'
end
If I change the method name from destroy to something like remove_me and change the action name to remove_me in the view, everything works as expected.
In the above two weird problems, I am sure there is no tricky routing set in my configuration.
All in all, seems the destroy and delete are mysterious keywords in rails. Can anyone explain this to me?
You probably set MyObject as a resource in routes.rb. Resources get a couple of routes that don't directly match the name of the action. When you use an action name that does not match the routes defined by the resource, you'll get the default route which directly maps to the name of the action.
I found that this link explains rails' routing very well. Take a look at the "RESTful routing" section.
If you are using REST routing, destory only support delete method. you can change your code like this
link_to "remove", :action => 'destroy', :id => myrecord", :method => :delete
Adding :method => :delete
rails will add a hidden input with name "_method", value "delete"
Replace all :post => true with :method => :post
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