Rails Routing Error for nested form_for - ruby-on-rails-3

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

Related

passing params through a link_to rails 3

Im just looking for some clarification on the following piece of code, well part of it.To give some background i have an app where you can upload recipes, search recipes and save them as favourites, this piece of code is in a controller "recipes", action is "my_recipes"
<%= link_to "Add to favorites", {:controller => 'favourites', :action => 'create', :recipe_id => recipe.id}, {:method => :post } %>
My understanding is that this creates a link_to (anchor tag if you will) that makes a post request through the create method within the favourites controller. This part I think i underdstand (corrections welcome), the part i am unsure of is
:recipe_id => recipe.id}
I know this is passing the recipe_id for example but I would like to know why we do this? and what relevance of the : before the first recipe_id.May seem obvious to some but you dont know until you learn.
Any help appreciated
Is this code in a partial? Is recipe being passed along? You should rewrite as so:
link_to "Add to favorites", new_favourite_path(recipe), method: :post
Do rake routes in your console and find out what the path is for creating favourites, then replace 'new_favourite' with that above. Note, the route might be identified with something more explicit like new_favourite_recipe.
To answer you question, you must pass recipe, or recipe.id because otherwise the controller wouldn't know which recipe to add to the favourites. You don't need to specify the user as that should be accessed directly from within the controller action using something like current_user.

How can I use a model from a gem in Rails 3?

I have a gem that has some ActiveRecord-derived models. They are tested and work.
I've added a dependency to that gem, but when I try to access pages that refer to that model in a form_for statement, for example, I get the dreaded method_missing error:
undefined methodmygem_mymodel_path'`
Note I had the models in my app/models directory as is usual and all was well; migrating my models to this gem has been the cause for grief.
UPDATE 1:
In response to Robin's question:
> rails console
> MyModel
=> MyGem::MyModel(id:string, name:string)
Update 2: For Robin's request for form code
Form erb:
<%= form_for(MyModel.new, :remote => true, :html => { :class => "new_mymodel_form", :id => "new_mymodel_form"}) do |f| -%>
After Robin's suggestions, this is the only way I've found around it:
UPDATE 3: a ugly workaround
With a model called Mymodel and a module called MyModule:
post '/mymodels', to: 'mymodels#create', as: 'my_module_mymodels
Because 'as' let's you control the path symbol name. I'd much rather use 'resources' macro, but it seems it doesn't know to add the module name to the path symbol when it creates it, even though form_for appends the module name. I assume there is a way to solve this, but I can't find anything about this scenario.
Use scope:
scope :as => "mymodule" do
resources :my_resources
end

Questions about rails3 routes

I'm upgrading my app to rails 3, and I am a bit confused about some of the routes. The resourceful ones are easy enough, but how can I set a generic rule for all actions in a specific controller. I tried something like this:
get 'custom/:action/' => {:controller => :custom}
But that didn't work. It seems the new format is "controller#action", but how can I specify the action to be variable?
Also, other than using named routes or resources, is it possible to do shorthand notation to name routes in a specific controller?
i.e. rather than:
get '/tasks', :controller => :home, :action => :tasks, :as => 'tasks_home'
get '/accounts', :controller => :home, :action => :accounts, :as => 'accounts_home'
is it possible to do something a little cleaner, like:
controller => :home do
get :tasks
get :accounts
end
And that would automatically created the named routes?
You can use action as a variable like this:
resource :custom do
match ':action'
end
This will generate
/custom/:action(.:format) customs#:action
custom POST /custom(.:format) customs#create
new_custom GET /custom/new(.:format) customs#new
edit_custom GET /custom/edit(.:format) customs#edit
GET /custom(.:format) customs#show
PUT /custom(.:format) customs#update
DELETE /custom(.:format) customs#destroy
So it will handle your action as a variable URL-s and will add some default CRUD actions as well.
Note that the controller name here is in plural. If you would like to use a route for a controller which name is in singular, use resources instead of resource.
The answer to the second question is almost identical to the first one, use resource:
resource :home do
get :tasks
get :accounts
end
generates:
tasks_home GET /home/tasks(.:format) homes#tasks
accounts_home GET /home/accounts(.:format) homes#accounts
home POST /home(.:format) homes#create
new_home GET /home/new(.:format) homes#new
edit_home GET /home/edit(.:format) homes#edit
GET /home(.:format) homes#show
PUT /home(.:format) homes#update
DELETE /home(.:format) homes#destroy
Note that the matched controller names are in plural again, because of the convention.
Looks like this is related to the persisted field being set to false on nested ActiveResource objects: https://github.com/rails/rails/pull/3107

Rails 3 Routing: Using 2 dynamic segments in path for one model

What I am trying to achieve is something similar to Github's way for routes. E.g. I have a project with the name 'question' results in the URL /hjuskewycz/question. So my goal is to have routes where the first segment is the username and the second the project's name.
I tried a couple of different approaches, this is the one I am stuck with right now:
scope ":username" do
resources :projects, :path => "" do
resources :pictures
end
end
Using
project_path :username => project.owner.username, :id => project.to_param
works as expected. However, it's tedious to always specify the username although it's always the owner's username. I would very much prefer
project_path(:id => project.to_param)
I know about default_url_options and url_for and I digged in the code. However, polymorphic_url doesn't use default_url_options.
I tried in routes.rb:
resources :projects, :path => "", :defaults => {:username => Proc.new { "just_testing" }}
since you can use a proc for constrains, but haven't got it working either.
I tried in project.rb
def to_param
"#{owner.username"/#{project.title}"
end
I spent already too much time on this problem and my current approach uses a convenience method to add the :username parameter. Nevertheless, I think using this method all over the place just to add an entry stinks (bad code smell). I wonder if there is a more elegant solution to this problem.
I think you should not make things complicated here, just use something like this:
In Routes.rb
match ':username/:projectname/' => 'projects#show_project' , :as => :show_project
and in project_controller, just define this
def show_project
#user =User.find_by_username(params[:username])
#project =Project.find_by_slug(params[:projectname])
end
Simpler is better, it saves time and easy to understand for others
You want to do something like this in your controller:
before_filter :set_username
def set_username
Rails.application.routes.default_url_options[:username] = #user.name
end

Rails 3 Routes: Named routes. "No route matches"

So I'm getting a 'No route matches' error, and being new to Rails 3 (and Rails in general), I really don't know what the problem is. Here are the pertinent routes:
resources :users
#...
match 'reset_password(/:reset_password_code)' => 'users#reset_password', :as => :reset_password, :via => :get
match 'reset_password' => 'users#reset_password_submit', :as => :reset_password, :via => :post
The GET method works fine. I get a routing error when the form POSTs that's generated on the get page, which starts like this.
<%= form_for #user, :url => reset_password_url do |f| %>
It looks like it's posting to the right spot, as the url is generated using 'reset_password_url', it's posting to it, and the url looks as it should... anyone have any ideas?
UPDATE
I'm using Rails 3.0.4
I've tried taking out every other route except for the ones that I've mentioned here, and I still can't figure out why it's not working.
Figured it out!
In my form, rails was (correctly) assuming that since I had a user that I was using with the form_for helper, that I wanted to update the user, not make a new one.
Therefore, it was using the PUT method to post my form. To solve the routing problem, I just had to change the last route to:
match 'reset_password' => 'users#reset_password_submit', :as => :reset_password, :via => :put
I only found the issue after using the Web Inspector in webkit to see the whole request, and looked at the _method parameter being sent in.