I have a problem with the normal way rails operates when using nested forms / resources and routing.
I have two tables, Words and Definitions...
Words have many definitions, but I do not create a Word until it has at least one definition.
Everything on the model and controller end works but I cannot figure out how to handle the form helpers.
<%= semantic_form_for [#word, #definition] do |f| %>
This works perfectly but only if #word actually exists and is not a new UNSAVED record. IE in the controller I am doing a find_or_initialize_by call for Word then building a definition off of that.
<%= semantic_form_for [:word, #definition] do |f| %>
This words but only if the word doesn't exist. IE if I try to edit using this construction I get an odd url (which doesn't work). words/12345/definition/12345
I tried using the url_for helper but had similar results as above...
Any other ideas?
Mongoid doesn't initialize embedded documents by default. You need to build them yourself most likely with a callback in your Word model:
after_initialize :build_definition
def build_definition
self.definitions.build unless self.definitions.any?
end
If you wanna stay CRUD and allow definitions to be created before words, you must duplicate routes for definitions, one inside words and one outside, so you can do:
<%= semantic_form_for [#definition] do |f| %>
I now this has been asked a thousand times but that doesn't help me heh :) I've been at this an hour. My form:
= form_for #comment, :url_for => { :action => "create", :controller => "comments"}, :method => :post
my rake routes:
POST /t/:trunk_id/r/:root_id/comments(.:format) {:action=>"create", :controller=>"comments"}
trunk_root_comment GET /t/:trunk_id/r/:root_id/comments/:id(.:format) {:action=>"show", :controller=>"comments"}
The error:
undefined method `comments_path' for #<#<Class:0x007fed2c713128>:0x007fed2c71cc78>
If I name space the form to:
= form_for [:trunk_root, #comment], :url_for => { :action => "create", :controller => "comments"}, :method => :post do |f|
which should make the route trunk_root_comments_path.. which is correct according to the rake routes.. I get:
No route matches {:controller=>"comments", :format=>nil}
Help is very much appreciated.. been looking at this for hours..
UPDATE:
Thank you Ryan for such a great answer! A very clear explanation of something I was just sort of 'throwing things' at, now at least I understand better. I actually already had 'trunk_root_comments_path' available in my rake routes, and I had tried a couple of the combinations you mentioned, but I wasn't really grocking what I was missing, so you helped. I'm using Mongo and I don't actually have a Trunk model, I just have an attribute on roots called #root.trunk, though I have a trunk controller and therefore its a part of my routes(maybe a bad idea idk).
So I tried your TLDR and it said error:
Undefined method 'root_comments_path'
.. cause no Trunk model exists, I assume?.. so I made #trunk just equal the correct id with
= form_for [#trunk, #root, #comment] do |f|
<- and I got 'undefined method `politics_root_comments_path''.. I figured well.. that probably makes sense.. since I'm failing I must as well try your most explicit version:
= form_for #comment, :url => (trunk_root_comments_path(:trunk_id => #root.trunk, :root_id => #root.id)) do |f|
and sure enough that worked... so I'm not quite sure how to do it shorter then this.. the odd thing for me is I have another nested resource "photos" at the same level of depth in the routes and I was able to get that to work with = form_for [:trunk_root, #photo], :html => { :class => 'root_form' } do |f|.. but here for some reason I couldn't.. anyways I'd say you gave me enough to understand 100% but I think I went from 20% understanding to 50% understanding.. I know now that id's ARE important to routes, and the named helpers need access to them. I got an introduction to how the url_helper works, but would need to read more on it to really grock it fully I think. I'm also now able to construct proper routes in their longer form at least to get through tricky situations like this. So thank you :)
TL;DR You need to specify both a :trunk_id and a root_id in your URL or use form_for like this:
<%= form_for [#trunk, #root, #comment] do |f| %>
Rails is attempting to build a URL from the hash you're giving it, but that hash doesn't match anything in its routing table. You could do this:
{ :controller => "comments", :action => "create", :trunk_id => trunk.id, :root_id => root.id }
But that's really a bit tl;dr.
The cooler way to do it is this:
trunk_root_comments_path(trunk, root)
Where trunk and root are Trunk and Root instances respectively.
Now, if you want to be super-wicked-cool, do it like this:
<%= form_for [trunk, root, comment] do |f| %>
Science!
So how does this work? Elementary, my dear:
Rails first recognises that we're using form_for using an Array and that we mean business. Rails uses this array passed in and builds a URL out of it. It does this by using the routing helpers that are defined by the routes. Unfortunately, you've defined your routes in a funny way that don't play nice with this, but don't fear! We can fix this.
The way you can do it is this where you have this in config/routes.rb:
post '/t/:trunk_id/r/:root_id/comments'
Instead put this:
post '/t/:trunk_id/r/:root_id/comments', :as => "trunk_root_comments"
You may alternatively already have this:
match '/t/:trunk_id/r/:root_id/comments', :via => :post
Which should become this:
match '/t/:trunk_id/r/:root_id/comments', :via => :post, :as => "trunk_root_comments"
Either way, you've now got not one, but two(!!) path helpers defined by the routes. These aretrunk_root_comments_path and trunk_root_comments_url respectively. The names of these methods are super important for what I am about to explain to you. Pay attention!
So, back to our little form_for call:
<%= form_for [trunk, root, comment] do |f| %>
Rails knows that we're using an Array because it can see it. What it does with this Array may seem like magic, but isn't really.
Rails will take each element of this array and build a routing helper method name up from the different parts. This isn't actually part of form_for, but another method called url_for that you can use by itself:
url_for([trunk, root, comment])
In the beginning, this routing helper method name generated by url_for is simply an empty array ([]). Nothing special at all.
But then what happens is special!
The first element is going to be a persisted instance of the Trunk class. By "persisted" I mean that it's an object that maps directly to a record in the database. Yay ORMs!
Rails will know this, and so will turn the routing helper into this: [:trunk].
The second element is going to be a persisted instance of the Root class. Rails also knows this (damn, Rails is smart!) and will then append this to the array, turning it into [:trunk, :root]. Awesome.
The third (and final) element is then checked by Rails. It sees that (in this case) it's a non-persisted element, i.e. it's not been saved to the database.. yet. Rails treats this differently and will instead append [:comments] to the array, turning it into this:
[:trunk, :root, :comments]
See where I'm going with this now?
Now that Rails has done it's thing (or thang, if you like) it will join these three parts together like this: trunk_root_comments, and just for good measure it'll put _path on the end of it, turning it into the final trunk_root_comments_path helper.
And then! Man, and then... Rails calls this method and passes it arguments! Just like this:
trunk_root_comments_path(:trunk_id => trunk.id, :root_id => root_id)
This generates a full path to the resource like this:
/t/:trunk_id/r/:root_id/comments
And bam! Full circle! That's how Rails will know to generate the URL and you don't have to use ugly hashes anymore.
Success!
Not sure if you have this route set up but try:
= form_for #comment, :url => trunk_root_comments_path
I have the following piece of code that I'm trying to fit into some generated scaffolding
= form_for(#event, :url => group_event_path(#event.group_id, #event) ) do |f|
As you can see, I've defined a nested resource route that looks like this
resources :groups do
resources :events
end
Now back to the form_for line above. The default Rails scaffolding uses code similar to above to generate _form, which is used in #new and #edit. The issue this presents to me is that form_for has to submit to these two paths
CREATE: group_events_path(#event.group_id)
UPDATE: group_event_path(#event.group_id, #event)
Is there a way for me to simplify this by modifying how the group_event(s)_path helpers work?
If you use the polymorphic form_for syntax, this will fix it:
= form_for([#group, #event]) do |f|
Now if that #event object is persisted in the database then it will use the update route, and if it's not then it will use the create route.
You can do the same thing with the normal form_for call:
= form_for(#event) do |f|
There's absolutely no reason to specify the :url option other than to customize the URL to be something different from what Rails infers.
I am building my first admin section in Rails and I am struggling with routing problems.
My routes.rb looks like this:
get "admin/menuh"
get 'admin/welcome'
namespace :admin do
resources :users
resources :menuh
resources :menuv
resources :welcome
end
And my views structure looks like views/admin/users/files. If I will set to url address of browser the url localhost:3000/admin/users/new, so I will get the error message No route matches {:controller=>"users"} (it's in the file views/admin/users/_form.html.erb - this file is generated by scaffold)... so I would like to ask you - where is the problem? Is here anything important, what I am disregarding?
You've set up your form_for like this, I reckon:
<%= form_for #user do |f| %>
Because the route is in a namespace, you need to tell the form that also:
<%= form_for [:admin, #user] do |f| %>
That should help you fix that issue.
I'm using the http://guides.rubyonrails.org/getting_started.html as an example to help me create my own application. I create the blog and comments modules just fine. When I add a method to the comments or blog controllers I cannot get a link_to action to work calling the new function. Everything points to a problem in the routes.rb but I've tried all the new syntax I've seen and nothing is working for me.
What I'm trying to do is create a simple execute method in the controller to run a ruby script and save the output to the database. Everything works according to the tutorial but when I try to extend the comment controller with a custom function called execute I cant get that to run.
comments_controller.rb #Same as destroy
def execute
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.destroy
redirect_to post_path(#post)
end
_comment.html.erb
<%= link_to 'Execute Comment', [comment.post, comment],
:method => :execute %>
routes.rb
resources :posts do
resources :comments do
get :execute, :on => :member
end
end
rake routes |grep execute
execute_post_comment GET /posts/:post_id/comments/:id/execute(.:format) {:action=>"execute", :controller=>"comments"}
Error when I click Execute comment link:
No route matches "/posts/3/comments/6"
run rake routes and see if there are any routes pointing to your controller action. If not you'll need to create one either as a "member action" or with a match rule.
If you do see the route, you can name it by passing an :as => route_name parameter to the routing rule. Doing so will enable the route_name_path() and route_name_url() helpers for your link_to
RailsCasts has a good quick rundown of the rails 3 routing syntax here
EDIT:
based on the code examples, try this :
<%= link_to 'Execute Comment', execute_post_comment_path(comment.post, comment) %>
According to the docs here the :method option can only contain valid http verbs (get, put, post, delete). The link_to helper can't puzzle out which action you want to hit with a custom member action, so you have to use the named route as above.
HTH