using form_for with a singular model - ruby-on-rails

I have a Wizard model that the client references w/o an ID (it's saved in the session), so I've created a singular resource for :show and :update. I want the admin to have access to all instances of that model via index so admin can delete strays, so I've added a plural resources for :index and :destroy. The index and destroy works, but I can't figure out the right arguments to pass to form_for in the update view.
The setup
# config/routes.rb
WTest::Application.routes.draw.do
resource :wizard, :only => [:show, :update]
resources :wizards, :only => [:index, :destroy]
...
end
resulting in
$ rake routes
wizards GET /wizards(.:format) {:action=>"index", :controller=>"wizards"}
wizard DELETE /wizards/:id(.:format) {:action=>"destroy", :controller=>"wizards"}
GET /wizard(.:format) {:action=>"show", :controller=>"wizards"}
PUT /wizard(.:format) {:action=>"update", :controller=>"wizards"}
This sets up routes the way I'd expect.
The question (revised since original post)
In the console:
>> app.wizard_path
raises the error ActionController::RoutingError: No route matches {:action=>"destroy", :controller=>"wizards"}
Why is this? Have I set up my routes incorrectly? I need to specify :url => wizard_path for form_for() in the wizards's update view.
The details
If I specify an explicit path in my call to form_for:
# app/view/wizards/update.html.erb
<%= form_for #wizard, :url => wizard_path do |f| %>
<%= f.submit %>
<% end %>
... then attempting to render this for gets an error on the form_for line:
No route matches {:action=>"destroy", :controller=>"wizards"}
I have no idea why it's trying to match the destroy action. How do I get the form to submit to the {action=>"update", :controller=>"wizards"} route?
(By the way, I looked at bug 267, and I don't think it is the same as what I'm observing. But if it is this bug, is there a workaround?)

Carrying on the long tradition of answering my own questions (meh!), I think I figured it out. If my analysis is wrong, I'd be happy to give someone else the checkmark...
The cause of the problem
Look at the output of rake routes
$ rake routes
wizards GET /wizards(.:format) {:action=>"index", :controller=>"wizards"}
wizard DELETE /wizards/:id(.:format) {:action=>"destroy", :controller=>"wizards"}
GET /wizard(.:format) {:action=>"show", :controller=>"wizards"}
PUT /wizard(.:format) {:action=>"update", :controller=>"wizards"}
The path method 'wizard_path' is ambiguous: it can either refer to the DELETE clause, in which case it needs an :id argument (wizard_path(22)), or it can refer to the GET and PUT clauses, in which case it doesn't take an ID argument.
The solution
So my solution was to create a route specifically for deletion. My revised routes.rb file now reads:
resources :wizards, :only => [:index]
resource :wizard, :only => [:show, :update]
match 'wizard/:id' => 'wizards#destroy', :via => :delete, :as => :delete_wizard
and rake routes now produces:
$ rake routes
wizards GET /wizards(.:format) {:action=>"index", :controller=>"wizards"}
wizard GET /wizard(.:format) {:action=>"show", :controller=>"wizards"}
PUT /wizard(.:format) {:action=>"update", :controller=>"wizards"}
delete_wizard DELETE /wizard/:id(.:format) {:controller=>"wizards", :action=>"destroy"}
I needed to make a one-line change to the delete link in wizards/index.html.erb to use the new delete_wizard_path, but everything works now.

Related

Rails: Sub-routing. No route matches

I have custom CRUD routes for example - for profiles
get '/profiles', to: 'profiles#index'
get '/profiles/new', to: 'profiles#new', :as => 'new_profile'
post '/profiles', to: 'profiles#create'
get '/profiles/edit/:id', to: 'profiles#edit', :as => 'profile'
patch '/profiles/edit/:id', to: 'profiles#update'
get '/profiles/get_profiles', to: 'profiles#get_profiles'
It works okay. But I do the same routing for profile skills, wich is under relation of profile. Routes of ProfileSkills looks like this
get '/profiles/:profile_id/profile_skills', to: 'profile_skills#index'
get '/profiles/:profile_id/profile_skills/new', to: 'profile_skills#new', :as => 'new_profile_skill'
post '/profiles/:profile_id/profile_skills', to: 'profile_skills#create'
get '/profiles/:profile_id/profile_skills//edit/:id', to: 'profile_skills#edit', :as => 'profile_skills'
patch '/profiles/:profile_id/profile_skills/edit/:id', to: 'profiles#update'
When I under the route for creating the new item
http://localhost:3000/profiles/1/profile_skills/new
It throw an exception
No route matches {:action=>"edit", :controller=>"profile_skills", :profile_id=>"1"}, missing required keys: [:id]
On form for line
<%= form_for #profile_skill do |form| %>
Why he don't understand that I'm under the 'new' route and it looking for 'edit', when i'm under the 'new'?
This problem is only when i'm on sub-routes. In 'Porfile' routes for example, if works fine.
In your routes use this
resources :profiles do
resources :profile_skills
end
this will provide you the routes like this
profiles/:profile_id/profile_skill points to index action of profile_skill
profiles/:profile_id/profile_skill/new points to new action of profile_skill
profiles/:profile_id/profile_skill/:profile_skill_id points to show action of profile_skill
profiles/:profile_id/profile_skill/:profile_skill_id/edit points to edit action of profile_skill
and so on.
for more help visit Rails Routing

Routes on Rails application are failing

I'm taking an MOOC and the goal of this exercise is to add a new functionality to typo, where i can merge two articles together.
When I add the route to my new function merge to the routes.rb I'm losing the functionality to delete articles. I think something clashes here, but I have no idea what.
The original routes.rb:
%w{advanced cache categories comments content profiles feedback general pages
resources sidebar textfilters themes trackbacks users settings tags redirects seo post_types }.each do |i|
match "/admin/#{i}", :to => "admin/#{i}#index", :format => false
match "/admin/#{i}(/:action(/:id))", :to => "admin/#{i}", :action => nil, :id => nil, :format => false
end
This method in articles.rb creates the correct url for deleting
def delete_url
blog.url_for(:controller => "/admin/content", :action =>"destroy",:id => id)
end
correct url:
http://example.com/admin/content/destroy/7
If i follow this link i can successfully delete an article.
However, if I add the following before that to my routes.rb:
namespace "admin" do
resources :content do
post :merge, on: :member, as: :merge
end
end
The new merging functionality and forms are working fine, but the method delete_url now produces something like this:
http://example.com/admin/content/7
and if I follow a link created by this method i get:
Unknown action
The action 'show' could not be found for Admin::ContentController
Maybe I'm overwriting something? I can't figure out what's happening here and why this affects the delete action / route.
Thanks in advance!
EDIT: rake routes | grep content:
with the original routes.rb gives me:
admin_content /admin/content {:controller=>"admin/content", :action=>"index"}
/admin/content(/:action(/:id)) {:action=>nil, :id=>nil, :controller=>"admin/content"}
whereas my modified routes.rb produces
merge_admin_content POST /admin/content/:id/merge(.:format) {:action=>"merge", :controller=>"admin/content"}
admin_content_index GET /admin/content(.:format) {:action=>"index", :controller=>"admin/content"}
POST /admin/content(.:format) {:action=>"create", :controller=>"admin/content"}
new_admin_content GET /admin/content/new(.:format) {:action=>"new", :controller=>"admin/content"}
edit_admin_content GET /admin/content/:id/edit(.:format) {:action=>"edit", :controller=>"admin/content"}
admin_content GET /admin/content/:id(.:format) {:action=>"show", :controller=>"admin/content"}
PUT /admin/content/:id(.:format) {:action=>"update", :controller=>"admin/content"}
DELETE /admin/content/:id(.:format) {:action=>"destroy", :controller=>"admin/content"}
/admin/content {:controller=>"admin/content", :action=>"index"}
/admin/content(/:action(/:id)) {:action=>nil, :id=>nil, :controller=>"admin/content"}
Okay, thanks to #guitarman i worked through my routes code and found out I can add the following except:
namespace "admin" do
resources :content, except: [:index, :show, :update, :destroy, :edit, :new, :create] do
post :merge, on: :member, as: :merge
end
end
after that, the rake routes just shows the additional merge route I wanted and my destroy action works fine again.
Check rake routes command. I think there is a route /admin/content/:id which will be created by resources :content in the namespace "admin".
Your request to http://example.com/admin/content/7 will be catched be the defined route but I gess you have no show action in the controller.
Better:
namespace "admin" do
post "/content/:id/merge", to: "admin/content#merge", as: :merge
end
For more information about recources and the CRUD operations please see the rails routing guide.

Can't access path specified by rake routes command

I can't seem to correctly understand the routing in rails 3.1.
(keeping in mind I'm working on a project that depends on the refinery cms gem)
In my routes I have the following:
Blog::Application.routes.draw do
resources :news, :as => :news_items, :controller => :news_items, :only => [:show, :index]
scope :module => "refinery" do
scope(:path => 'refinery', :as => 'refinery_admin', :module => 'admin') do
resources :news, :as => :news_items, :controller => :news_items
#resources :news, :except => :show, :as => :news_items, :controller => :news_items
end
end
end
The output of the rake routes command is:
news_items GET /news(.:format) {:action=>"index", :controller=>"news_items"}
news_item GET /news/:id(.:format) {:action=>"show", :controller=>"news_items"}
refinery_admin_news_items GET /refinery/news(.:format) {:action=>"index", :controller=>"refinery/admin/news_items"}
POST /refinery/news(.:format) {:action=>"create", :controller=>"refinery/admin/news_items"}
new_refinery_admin_news_item GET /refinery/news/new(.:format) {:action=>"new", :controller=>"refinery/admin/news_items"}
edit_refinery_admin_news_item GET /refinery/news/:id/edit(.:format) {:action=>"edit", :controller=>"refinery/admin/news_items"}
refinery_admin_news_item GET /refinery/news/:id(.:format) {:action=>"show", :controller=>"refinery/admin/news_items"}
PUT /refinery/news/:id(.:format) {:action=>"update", :controller=>"refinery/admin/news_items"}
DELETE /refinery/news/:id(.:format) {:action=>"destroy", :controller=>"refinery/admin/news_items"}
The following code in my application leads to an error:
<%= form_for [:refinery, #news_item] do |f| %>
<% end %>
Telling me that the following path:
undefined method `refinery_news_items_path' for #<#<Class:0x0000010663c480>:0x00000106623980>
doesn't exist.
Any path from the rake routes command would just not work. I'm pretty confident that's it's a setting issue. I'm actually kind of writing a plugin to the RefineryCMS gem without actually using the plugin generator, I'm just building it as if it was a normal web app.
My project is hosted here for those who care to take a peak. http://github.com/mabounassif/blog
Anyone knows what might be the problem?
You're scoping your "refinery" scope as "refinery_admin" so when you did your rake routes, you got refinery_admin_news_items
if you take out that :as => 'refinery_admin' clause, your routes will return back to "normal"
It seems that the problem was with the way the Refinery CMS gem works. Apparently I shouldn't use the url helper right away, I should use the following instead:
main_app.new_refinery_admin_news_item_path

Ruby on Rails: link_to action, no route matches

I'm getting into Rails and trying to add a "vote" feature on a blog setup from here: http://guides.rubyonrails.org/getting_started.html
In app/controllers/posts_controller.rb I created this:
def incvotes
#post = Post.find(params[:id])
post.update_attributes(:upvotes => 1 )
format.html { redirect_to(#post, :notice => 'Vote counted.') }
format.xml { head :ok }
end
In app/views/posts/index.html.erb I created this:
<%= link_to 'Vote', :controller => "posts", :action => "incvotes", :id => post.id %>
But the link is giving the error
No route matches {:controller=>"posts", :action=>"incvotes", :id=>1}
I'm missing something here, but not sure what.
rake routes:
incvotes_post POST /posts/:id/incvotes(.:format) {:action=>"incvotes", :controller=>"posts"}
posts GET /posts(.:format) {:action=>"index", :controller=>"posts"}
POST /posts(.:format) {:action=>"create", :controller=>"posts"}
new_post GET /posts/new(.:format) {:action=>"new", :controller=>"posts"}
edit_post GET /posts/:id/edit(.:format) {:action=>"edit", :controller=>"posts"}
post GET /posts/:id(.:format) {:action=>"show", :controller=>"posts"}
PUT /posts/:id(.:format) {:action=>"update", :controller=>"posts"}
DELETE /posts/:id(.:format) {:action=>"destroy", :controller=>"posts"}
home_index GET /home/index(.:format) {:action=>"index", :controller=>"home"}
root /(.:format) {:action=>"index", :controller=>"home"}
try
= link_to "vote", incvotes_post_path(post), :method=>:post
and if that doesn't work, try changing the method to :put
My guess is that you probably do not have a definition in your routes file for the action you just defined in the controller. Both an action in the controller and an action in the routes file must be defined for Rails to generate urls correctly.
Your routes file probably has something like this:
resources :posts
But you want to add more than the standard actions generated by the resources keyword, so try something like this:
resources :posts do
member do
post 'incvotes'
end
end
This tells routes that you have another action in your posts controller called incvotes that accepts HTTP post requests as long as they are pointed at a member route with the correct action (/posts/14 is a member route, while /posts/ is a 'collection' route). So you will have a new route probably like /posts/14/incvotes that you can post a form to and everything should start working properly.
EDIT:
Actually I guess since you are just adding 1 to an attribute on a model, you don't need a POST action (which are normally associated with posting forms as with create and update). To send a post, you might need to change the HTML in the view to include a form and have it post to the correct url. So you can try that, or you can change your routes file to read get 'incvotes' instead of post 'incvotes'. Sorry for the confusion, hope that helps!
The incvotes_post route only accepts a HTTP POST, and a link always produces a HTTP GET.
Use a form with a button instead (or do a POST using AJAX).
Try using button_to instead link_to:
In your view:
<%= button_to 'Vote', incvotes_post_path(post) %>
In your config/routes.rb add the route to incvotes action as post:
resources :posts do
member do
post 'incvotes'
end
end
And in your controller, create the incvotes action:
def incvotes
# Something
redirect_to :posts
end

Rails controller edit action - how to avoid the id to be displayed?

I have a resource named offer. When I edit the offer the URL looks like “/offers/1/edit”. How is it possible to avoid the ID to be displayed in the URL completely?
I want to store the offer ID in the session instead as the offer actions should be public. I’m afraid if one would try to enter different IDs in the URL manually. Is that possible?
My desired URL would look like this: “/offers/edit”.
The create action responds this: render "edit". Here the ID is not displayed, the URL is just “/offers”, fine.
But this response (render "edit") leads to displaying the URL with the ID again, grrr.
Is there a solution to that?
Thanks for your help!
Marc
You can overwrite method to_param in your model
def to_param
your_item.name.parameterize
end
then router will produce something like this “/offers/name_of_your_item/edit”.
You could (although it is a kind of misuse) use a resource mapping instead of a resources.
You could add resource :offers to your routes.rb.
That will give you routes like this:
offers POST /offers(.:format) {:action=>"create", :controller=>"offers"}
new_offers GET /offers/new(.:format) {:action=>"new", :controller=>"offers"}
edit_offers GET /offers/edit(.:format) {:action=>"edit", :controller=>"offers"}
GET /offers(.:format) {:action=>"show", :controller=>"offers"}
PUT /offers(.:format) {:action=>"update", :controller=>"offers"}
DELETE /offers(.:format) {:action=>"destroy", :controller=>"offers"}
Alternatively I would simply not use the standard edit mapping and add my own:
resources :offers, :except => [:edit, :update] do
get :edit, :on => :collection
put :update, :on => :collection
end
If my memory serves me right then the following should work in Rails 2.3.x:
map.resources :offers, :except => [:edit, :update], :collection => { :edit => :get, :update => :put }
I don't have such environment handy so I cannot test it at the moment...

Resources