Modify a URL helper for a path? - ruby-on-rails-3

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.

Related

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

Rails syntax Passing POST parameters from a form to a controller

I'm new to Rails (and fairly new to programming in general) and I am building a web app for myself as a way to learn. Right now I am modifying scaffolded forms and such.
My question is with the "create" method in one of my controllers. There are two entities I am concerned with: the User table and the Habit table. I created a dropdown box in the _form partial for the Habit views to allow a person to select a user from a list of all available when creating a habit as below
<%= collection_select :user, :id, #users, :id, :first_name %>
The habit controller, of course, has
def new
#users = User.all
...
end
This works fine, and when the form submits it posts two hashes of parameters :habit and :user. Now, when I want to process the form input in the create method, I'm not sure how to use the syntax correctly and assign the user_id to the newly create habit. What I WANT to do is something like this
def create
#habit = Habit.new(params[:habit], params[:user])
end
This, of course, is improper syntax.
def create
#habit = Habit.new(params[:habit])
end
assigns the params from the :habit hash correctly, but then the user_id is left unset.
What works is the following, but the code is very lengthy, assigning each value manually.
def create
#habit = Habit.new(:user_id => params[:user][:id],
:description => params[:habit][:description],
:habit_method => params[:habit][:habit_method],
:time_reqd => params[:habit][:time_reqd],
:will_reqd => params[:habit][:will_reqd],
:active => params[:habit][:active])
end
So my question is, when dealing with a form that posts data in multiple hashes, what is the proper way to pass those parameters into some method in a controller?
So my question is, when dealing with a form that posts data in multiple hashes, what is the proper way to pass those parameters into some method in a controller?
Instead of saying Habit.new( <lots of stuff> ), just use Habit.new(params[:habit]). Rails will try to assign each key in the hash (in this case, the params[:habit] hash's keys) to a matching value on the object.
Thus, if params[:habit] has a :description key, it will be assigned to a field called description on your model. This is called mass assignment and is quite handy.
Now you can just do:
#habit = Habit.new(params[:habit])
#habit.user_id = params[:user][:id]
You may want to read the RoR Getting Started Guide, like this section, for more similarly handy features of Rails.
Change
<%= collection_select  :user, :id, #users, :id, :first_name %>
To
<%= collection_select  :habit, :user_id, #users, :id, :first_name %>
The existing scaffold code should just work after that
Alternate
<%= f.select :user_id, #users, :id, :first_name %>

Rails 3 how to add a custom method to controller

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