Rails Nested Singular Resource Routing Appending .ID - ruby-on-rails-3

I have the following nested singular route:
resources :listings do
resource :logo, only: [ :edit, :update ]
end
It generates 2 correct routes as expected:
edit_listing_logo GET /listings/:listing_id/logo/edit(.:format) logos#edit
listing_logo PUT /listings/:listing_id/logo(.:format) logos#update
Now when I redirect to edit_listing_logo_path
redirect_to edit_listing_logo_path( #listing, #logo )
or when I create an update form
<%= form_for [ #listing, #logo ] do |f| %>
the resulting link has always singular resource ID attached at the end like this
/listings/2/logo.1
I'm not using respond formats in this app yet so it's working fine. But this link generation seems strange and I expect it to cause problems if used with various respond formats.
Note, that this was also discussed here Rails Nested Singular Resource Routing but I'm not using 'show' path at all.
Thanks for any inputs.

I believe you can get the answer you need either by using the verbose path method edit_listing_logo_path(#listing) (as mentioned by #Martin Sojka) or by using a symbol instead of the object itself.
So instead of using the normal form:
form_for [ #listing, #logo ]
you can dispose of the object specificity (and the trailing ID) by writing:
form_for [#listing :logo]
and if you want to call another action on the controller you can prefix it in the array:
form_for [:edit, #listing :logo]
Hope it works for you - it seemed to solve my issues.

Related

Rails 5.1 Routes: dynamic :action parameters

Rails 5.0.0.beta4 introduced a deprecation warning on routes containing dynamic :action and :controller segments:
DEPRECATION WARNING: Using a dynamic :action segment in a route is deprecated and will be removed in Rails 5.1.
The commit message from this PR states:
Allowing :controller and :action values to be specified via the path
in config/routes.rb has been an underlying cause of a number of issues
in Rails that have resulted in security releases. In light of this
it's better that controllers and actions are explicitly whitelisted
rather than trying to blacklist or sanitize 'bad' values.
How would you go about "whitelisting" a set of action parameters? I have the following in my routes file, which are raising the deprecation warning:
namespace :integrations do
get 'stripe(/:action)', controller: 'stripe', as: "stripe"
post 'stripe/deactivate', controller: 'stripe', action: 'deactivate'
end
Though it's a bit cumbersome, the best approach seems to be to explicitly define the routes:
namespace :integrations do
namespace 'stripe' do
%w(auth webhook activate).each do |action|
get action, action: action
end
end
post 'stripe/deactivate', controller: 'stripe', action: 'deactivate'
end
Is not the same case as you, but I did this:
class PagesController < ApplicationController
def index
render params[:path]
end
end
Routes:
get ':path', to: 'pages#index'
I suppose if I want a nested path I will use *:
get '*path', to: 'pages#index'
It works like this:
get 'stripe(/:action)', controller: 'stripe', action: :action, as: "stripe"

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

rails nested resource and routes for initialized resource

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| %>

Rails Routing Error for nested form_for

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

Modify a URL helper for a 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.