passing params through a link_to rails 3 - ruby-on-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.

Related

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

Beginner struggling with update_attribute command

I am in the process of trying to use the update_attribute command, but struggling to get it working (at all) and hoped someone could point me in the right direction?
I have previously posted a question about this issue, it was very useful in terms of giving a feel for the mechanics of what is going on, but unfortunately it didn't actually get it working.
I have a database of items (Items), which among other things contains ':item_name', ':click_count' and ':external_url'.
Currently I have a view (Showselecteditems) in which there is a list of all the items, when a user clicks on an item name, they are directed to the appropriate external url. This works using the code:
<%= link_to selecteditem.item_name.to_s, selecteditem.external_url %>
I would now like to add the ability to count the number of times a particular item name has been clicked on (i.e. in total for all users, not individual users) and therefore the number of times each external url has been visited in order to work out which is most popular.
Reading around, I believe i need to modify the code above to something of the form:
<%= link_to selecteditem.item_name.to_s, selecteditem.external_url, {:controller => params[:controller], :action => clickcountplusone, :identifier => selecteditem.item_name} %>
And need to define this function somewhere - it seems to only be found if located in 'application_helper'?
def clickcountplusone
clickeditem = Items.find(params[:identifier])
clickeditem.update_attribute(:click_count, clickeditem.click_count + 1)
rescue ActiveRecord::RecordNotFound # to avoid error if no identifier value
end
Needless to say, I cannot get this to work... My question is therefore, how can I set things up correctly so that when the link is clicked on the count is incremented? The other common problem people seem to report is that the number will be incremented each time the page is refreshed, which I would like to avod if possible.
Previously people have suggested adding to the 'show' section of the 'Items' controller, however, i don't know how this would work as the links are being clicked on the Showselecteditems view page, not the database itself where you get the show, edit, destroy commands. Any advice greatly appreciated.
This
<%= link_to selecteditem.item_name.to_s, selecteditem.external_url, {:controller => params[:controller], :action => clickcountplusone, :identifier => selecteditem.item_name} %>
will not point user to the some_controller#clickcountplusone, because you already specified an external link.
The easiest way to do this job is to modify your link_to like:
<%= link_to selecteditem.item_name.to_s, {:controller => params[:controller], :action => clickcountplusone, :identifier => selecteditem.item_name} %>
And then to modify your actions source:
def clickcountplusone
clickeditem = Items.find(params[:identifier])
redirect_to clickeditem.external_url if clickeditem.update_attribute(:click_count, clickeditem.click_count + 1)
rescue ActiveRecord::RecordNotFound # to avoid error if no identifier value
end

Rails 3 create new child for a specific parent

Hey all! Just joined up on stack overflow, as it has been a helpful resource while starting to learn about Ruby on Rails 3.
I can't seem to find one particular answer though, and maybe I'm barking up the wrong tree for all I know, but hopefully you folks can sort me out here. First some info on what I'm working with:
- In my web app I have 2 models: Projects and Tasks in a one-to-many relationship.
- Projects has many Tasks, and Tasks belong to Project
- Tasks IS NOT a nested resource, as users need to be able to see all current tasks, regardless of which project they are for.
- routes.rb therefore looks like this right now:
resources :projects
resources :tasks
In the project show view I display a list of tasks associated with that project. below that there is a link_to for creating a new task that looks like <%= link_to 'New Task', new_task_path, :class => "new-btn" %>. The link_to takes user to the new view for creating a new task. The rendered _form view starts with: <%= form_for(#task) do |f| %>.
Now, I think I need to pass the project id from the project show view, to the new task view: but, this is where I am getting lost and possibly, a bit mixed up.
Could someone please point me in the right direction: maybe to a resource outlining all steps involved in doing this, or maybe even provide an outline of the steps involved in the process here.
Many thanks!
You may create nested routes and still be able to show all tasks regardless of the project. Just make sure you have task resource defined later, like
resources :projects do |p|
resources :tasks
end
resources :tasks
Now in projects index or show view you can create link like
link_to 'New Task', new_project_task_path(project)
The task index view may be a little bit tricky. All depends on how you sort these tasks. If, for example, you show them sorted by project then you can create a link like
link_to 'New Task', new_project_task_path(task.project)
As for forms. In new action you have to get the id from params and put it into task object
if (params[:project_id])
#task.project_id = params[:project_id]
In form view you may create hidden field that will save this value
f.hidden :project_id
This will make it work, but you will not be able to go to the new task without providing project. Better solution it would be to create a select field with all projects. To accomplish that you should do the following:
in models/project.rb
def getProjectsList
projects = Project.all
projects.map do |p|
[project.name, project_id]
end
end
in controllers/application_controller.rb
def find_projects
#projects = Project.new.getProjectsList
end
in controllers/tasks_controller.rb at the begining
before_filter :find_projects, :only => [:new, :edit, :update, :create]
in views/tasks/_form
f.select :project_id, #projects
This way you can always select project and in case there is one given in params it will be already selected
If current_user returns User object then you should be able to call
current_user.projects
to get all user's projects.
Defining a relation between user and task may be working (though I am not sure this one).
#models/user.rb
has_many :projects
has_many :tasks, :through => :projects
In this case simply
current_user.tasks
should return user's tasks
There are many ways to do it. An easy one may be to add a parameter to you link and use it in the controller:
In your view:
<%= link_to 'New Task', new_task_path(:project_id => #project.id), :class => "new-btn" %>
In your tasks controller:
def new
#task = Task.new(:project_id => params[:project_id])
end
One way to pass the project ID from the project page to the New Task page is to add it to the query string on your url. Example HTML would look like this:
New Task
To get Rails to generate that HTML, you can do this in your ERB:
<%= link_to 'New Task', new_task_path(:project_id=>#project.id), :class => "new-btn" %>
Next, you need to pass the project ID from New Task page to the action that actually creates the Task. One way to do that would be to make a hidden input inside your form that contains the project ID so that it will be passed along with the other parameters when the form is submitted. The HTML would look like:
<input type="hidden" name="project_id" value="<%= params[:project_id] %>" />
To do this the Rails way, you can set the project ID in the new task action in the TasksController:
#task.project_id = params[:project_id]
and then do something like this in your view inside the form_for block (I'm not 100% sure on the syntax):
<%= f.hidden_field(:project_id) %>
Wow! Thanks for all the great info guys! I definitely learned a few neat tricks going through this.
Here is what I have working now:
routes.rb
resources :projects do |p|
resources :tasks
end
resources :tasks
I'm really happy to learn that that part is possible. Now I can enjoy the benefits of nested resource, but use the original non-nested routes for tasks, as well.
the link_to in show project view
<%= link_to 'New Task', new_project_task_path(#project), :class => "new-btn" %>
tasks_controller.rb new action
if (params[:project_id])
#task.project_id = params[:project_id]
end
new task form hidden field
<%= f.hidden_field :project_id %>
That all works great for adding new tasks to projects: but, showing a list of all tasks, related to all projects, that are related to the current user was a little bit trickier, and I wonder if there might be a better way than what I came up with:
tasks_controller.rb in the index action
#projects = Project.find_all_by_user_id(current_user)
#tasks = Array.new
#projects.each do |p|
p.tasks.each do |t|
#tasks << t
end
end
I'm using the "devise" and "cancan" gems for user management(both have been great!). The "current_user" above is simply what you would expect: the currently logged in user. Is this a reasonable solution, or is there a better way of getting all tasks for a user?
Just in case:
User has_many Projects, and Project has_many Tasks

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