URL helper method uses what default :id? - ruby-on-rails

I'm new to Rails and had a doubt regarding the link_to method. The second argument should be the url of the link, which one can generate using the url helper methods. However, one may need to pass :id as an argument of the helper method, which can be done by passing an object (which has :id as one of its attributes).
Well, in one case I did not pass the object to the method (in one of the views). However, the url was still able to obtain the correct :id (presumably using an instance variable defined earlier).
How did Rails choose a value for :id when I didn't even pass in any object?
Thanks a lot!
Edit
Here's the relevant code:
link_to 'Find Movies With Same Director', same_dir_path
Here, I am on a "show" page with url /movies/1. The same_dir_path is the helper method for the URL /movies/same_dir/:id where :id would be that of the passed object and movie#same_dir is the controller#action. Note I did not pass any object to the helper method link_to and yet, it takes the :id from the previous url ('1' in this case). The URL isn't even relative to the previous one (the path is different).
This is the controller method (same_dir):
def same_dir
#movies = Movie.find(params[:id])
if (#movies.director.nil? || #movies.director == '')
flash.keep
redirect_to movies_path
flash[:warning]="'#{#movies.title}' has no director info"
return
end
#otherMovies = Movie.find_all_by_director(#movies.director)
end
This is the routes.rb code:
match 'movies/same_dir/:id'=> 'movies#same_dir', :as => :same_dir
resources :movies

After reading your updated question I can provide you with a better answer:
Rails controllers can have default url options via the url_options method. (Doesn't seem to be a very documented feature. (here and here))
By default this method returns the parameters from the current request and that is where the id is coming from.
You can override it, too:
def url_options
{ my_parameter: my_value }.merge(super)
end
Original answer (might still be useful):
What you are witnessing is most likely a browser feature. For example this code:
link_to "Show", ""
generates this HTML code:
Show
If you click that link in a browser it navigates to the empty url relative to the current url, which is in fact equal to the current url.
Another example:
link_to "Publish", :publish
generates:
Publish
Now if your current url is http://localhost/articles/1/edit that link will take you to http://localhost/articles/1/publish. (Notice that the final url contains the model ID even though you are not having it in the HTML source)
In both cases your current model ID is preserved by the browser because you are using relative urls.
This behaviour might give you the illusion of some magical model ID detection, especially because browsers preview the final (=absolute) url when hovering over the link.
Have a look at the source, I'll bet your generated links do not contain any model IDs.

Related

How does edit_password_reset_url(some_id_here) resolve to https://example.com/password_resets/some_id_here/edit?

I've been looking at Learn RoR tutorials and we have this route:
edit_password_reset GET /password_resets/:id/edit(.:format) password_resets#edit
I do not understand how this line:
<%= edit_password_reset_url(#user.reset_token, email: #user.email) %>
Will translate into:
https://example.com/password_resets/<some token here>/edit?email=<user email here>
More specifically, how does rails know that the first param (#user.reset_token) is suppose to go into the :id portion of the url?
The controller PasswordResetsController's edit function is defined but blank.
The first argument passed to edit_password_reset_url replaces the :id parameter you see when running rails routes (/password_resets/:id/edit(.:format)). The docs on this can be found here.
You can actually update this to use a more relevant parameter, such as :token in your case, by using:
resources :password_resets, param: :token
The url helper also takes a hash of arguments where you can provide additional params, as you're doing with email.
Slightly unnecessary additional explanation
Were you using a nested route, say, edit_user_password_reset_url, this would product something akin to users/:id/password_resets/:token/edit(.:format). The same still applies - the first arg of the helper fills in the variable parameters, though would now take an array, i.e. edit_user_password_reset_url([#user.id, #token])
/end slightly unnecessary additional explanation
The edit action in your controller can be blank, though typically assigns a resource to be used in the edit view (/password_resets/edit.html.erb or similar). This generally contains a form that, when submitted, will hit the update action of the same controller.
So, in this case, you may want the edit action to contain the following:
def edit
#user = User.find_by_reset_token(params[:id]) # or params[:token] if you update as above
end
Then, in your edit view, you can include a form allowing the user to reset their password.

Overriden route id field is being reset

In one of my models i have a field called token, that is created with before_create. It is never modified again.
In routes.rb i am using it instead of id, like: resources :model, param: :token
So for example the edit route is now model/:token/edit.
In my actions i am doing find_by(:token, params[:token]).
I have a partial form created with simple_form which is being loaded in the new and edit actions.
This works fine, the routes are generated properly, all showing :token: instead of :id.
The show action works fine. In the show page i have a link_to which links to the edit view. This works fine also.
Both are using :token in the route, the edit view loads the models fields, everything is normal.
However when you look at the source code for the edit view, it shows the action as /model/<id>, instead of /model/<token>. For example /model/5.
When you submit the edit form, it tries to go to /model/5/.
In addition, when i checked the params being sent, it shows token set to the value of id. So somehow, the token field has been reset.
So
1) The token field is somehow being reset to the value of id. I have no idea how this is happening.
2) simple_form seems to be generating the action based off of id instead of token. However i have realized that since token is being reset, it could be that simple_form isnt doing anything wrong and is using the value of token.
The only fix i could come up with, was setting the url field in simple_form_for manually, but if i do that, it then breaks the new action.
For simple_form I'm just doing:
<%= simple_form_for #model do |f| %>
In routes.rb I'm doing:
resources :model, param: :token
In the controller, the edit action is:
#model = Model.find_by(token: params[:token])
The update action is:
#model = Model.find_by(token: params[:token])
if #mode.update model_params
redirect_to model_path #model.token
else
render 'edit'
end
Nothing fancy in the least.
The simple_form uses the default behavior of Rails when generating the route string for you. Since the config/routes.rb just provide the named pattern matching for the Outside In URL, so it is not too much meaning for the application from inside. For ex, with a route like this
model/:token/edit
just means any string between model/ and /edit will be assigned to params[:token]. It doesn't mean that string has to be the value of YourModel#token. Of course, you can assign that pattern to params[:foobar] by the same way without breaking anything model/:foobar/edit
For fully replace id key by token key, you have to override the method YourModel#to_param
# app/models/your_model.rb
class YourModel < ActiveRecord::Base
def to_param
token
end
end
So that, the ActiveSupport will know to use token as the value when generating routing pattern from inside out.
You can read more about it at the Rails's Guides

Why is link_to running a method inside it when the view is visited?

I have this link in the view:
<%= link_to "Pay in PayPal", #order.paypal_url(edit_order_url, payment_notification_url), class: "btn btn-success" %>
And this in the model:
class Order < ActiveRecord::Base
def paypal_url(return_url, notify_url)
# ... Do stuff
self.update_attribute(:payment_in_process, true)
# Do more stuff...
end
When I visit the view, the model gets its attribute updated. I do not want this. I only want the attribute updated if the link is clicked.
Know how to accomplish this?
Let me start with a brief overview of link_to method. According to api, this method creates a link tag of the given name using a URL created by the set of options:
link_to(name = nil, options = nil, html_options = nil, &block)
In your particular case, the name is "Pay in PayPal", which is a plain string. The second parameter, URL, is a bit more complicated, as you do not pass any kind of an absolute or relative path to content on the website, but call the method on an object instead:
#order.paypal_url(edit_order_url, payment_notification_url)
What happens here is that particular method paypal_url being instantly called, and link_to expects this method to return a correct URL. The same happens with the familiar examples from documentation, e.g.:
link_to "Profile", profile_path(#profile)
# => Profile
Here profile_path is a method which accepts an argument to get its unique id and therefore being able to generate a correct path. Same rule applies here: this method gets called as soon as user requests the page with the link.
So the first thing to do is to remove everything related to postprocessing of a click out of paypal_url method.
But you want to make those things work after a link is clicked. Actually a decision on whether a user clicked a link (or not) is made in a controller layer. Saying it simply, in case a controller action is called, then you know that a user clicked a link.
So the step two is to make some kind of request to your inner controller action and put the related logics there. In case paypal_url leads a user to your inner website page, you probably have that action implemented already. On the other hand, your paypal_url might lead to outer page, like actual paypal processing... In that case you could achieve the same with redirection or ajax request.

Problem Sending Value to New Action in Rails 3

I am currently trying to define my first new action in Rails 3, despite various problems I think things are almost done now. However, I as a final hurdle I am struggling to send the correct parameter value to my function...
My function is defined in items_controller:
def clickcountplusone
clickeditem = Item.find(params[:id])
redirect_to clickeditem.externalurl if clickeditem.update_attribute(:click_count, clickeditem.click_count + 1)
end
routes.rb contains:
match '/items/clickcountplusone', :to => 'items#clickcountplusone'
and the view contains:
<%= link_to image_tag( item.picture.to_s + ".gif", send("items_clickcountplusone_path", item.item_name)%>
The view page itself loads correctly (the one with the link on it), but when I click on the link I get an error:
Couldn't find Item with ID=clickcountplusone
{"id"=>"clickcountplusone",
"format"=>"whatever the name is"}
and rather than going to the external page, my browser tries to load:
http://localhost:3000/items/clickcountplusone.whatever the name is
Can anyone tell me how I should be calling the function so that the ID is the item_name and the external URL is visited rather than an incorrect one on my site?
Thanks in advance
It seems like this would be a normal route, instead of a RESTful route (this is fine). There are some places you have to change.
First, in your controller's action, you used params[:id] which is not set actually.
In this particular case, I would suggest you use params[:item_name] instead of id because you are really sending the item_name.
def clickcountplusone
clickeditem = Item.find_by_item_name(params[:item_name])
redirect_to clickeditem.externalurl if clickeditem.update_attribute(:click_count, clickeditem.click_count + 1)
end
Item.find could only be used if the parameter is one of the actual id / :all / :first / :last.
You are finding by the item_name, so you should use Item.find_by_item_name instead.
Second, you have to update you route too (or else you would need something like /you_path..?item_name=blahblahblah which is fine too if you don't mind)
get 'items/:item_name' => 'items#clickcountplusone', :as => :items_clickcountplusone
Third, you view. IMO, most of the time if you are using send but not writing library / really back end code, you probably misusing it.
<%= link_to image_tag("#{item.picture.to_s}.gif"), items_clickcountplusone_path(:item_name => item.item_name) %>
You don't have any variable in your match statement. Try something like
match '/items/clickcountplusone/:id', :to => 'items#clickcountplusone'
and
<%= link_to image_tag(item.picture.to_s + ".gif", items_clickcountplusone_path(:id => item.item_name))%>

Rails - link_to, routes and nested resources

As my understanding on nested resources, on edge Rails, should not
link_to 'User posts', #user.posts
point to
/users/:id/posts
?
The routes.rb file contains
map.resources :users, :has_many => :posts
If this is not the default behavior, can it be accomplished doing something else?
Along the same lines as Rishav:
link_to "User Posts", [#user, :posts]
Here's an explanation from my blog.
Really early on in Rails, you would write routes like this:
redirect_to :controller => "posts", :action => "show", :id => #post.id
What this would do is dutifully redirect to the show action inside the PostsController and pass along the id parameter with a
value of whatever #post.id returns. Typical 302 response.
Then Rails 1.2 came along and allowed you to use routing helpers, like this:
redirect_to post_path(#post)
And the people rejoiced.
This would do effectively the same thing. post_path here would build a route using the #post object that would look something
like /posts/1 and then redirect_to would send back a 302 response to that route and the browser would follow it.
Then later versions (I can't remember which one), allowed syntax like this:
redirect_to #post
And the people rejoiced a second time.
Magic, but not really
Any sufficiently advanced technology is indistinguishable from magic.
While this seems like magic, it's not. What this is doing is actually very, very neat. The redirect_to method, much like its cousins link_to and form_for all use a common method to build URLs, called url_for. The url_for method takes many different
varieties of objects, such as strings, hashes or even instances of models, like in the example above.
What it does with these objects then, is quite neat. In the case of the redirect_to #post call above, it inspects the #post
object, sees that it is an object of the Post class (we assume, anyway) and checks to see if that object has been persisted in a
database somewhere by calling persisted? on it.
By "persisted", I mean that a Ruby object has a matching record in the database somewhere. The persisted? method in Active Record is implemented like this:
def persisted?
!(new_record? || destroyed?)
end
If the object wasn't created through a call such as Model.new then it won't be a new record, and if it hasn't had the destroy method called on it won't be
destroyed either. If both of these cases are true, then that makes the object has most likely been persisted to the database in the form of a record.
If it has been persisted, then url_for knows that this object can be found
somewhere, and that the place it can be found is most likely under a method called post_path. So it calls this method, and passes
in the to_param value of this object which is usually the id.
In short, it's effectively doing this:
#{#post.class.downcase}_path(#post.to_param)
Which comes out to being this:
post_path(1)
And when that method is called you would get this little string:
"/posts/1"
Lovely!
This is called polymorphic routing. You can pass an object to methods like redirect_to, link_to and form_for and it will
attempt to work out the correct URL of what to use.
The form of form_for
Now, when you're coding Rails you may have used form_for like this a very long time ago:
<% form_for #post, :url => { :controller => "posts", :action => "create" } do |f| %>
Of course, with advancements in Rails you could simplify it to this:
<% form_for #post, :url => posts_path do |f| %>
Because the form is going to default to having a POST HTTP method and therefore a request to posts_path is going to go to the
create action of PostsController, rather than the index action, which is what would result if it were a GET request.
But why stop there? Why not just write this?
<%= form_for #post do |f| %>
Personally, I see no reason not to... if it's something as simple as this. The form_for method uses url_for underneath, just like
redirect_to to work out where the form should go. It knows that the #post object is of the Post class (again, we assume) and it
checks to see if the object is persisted. If it is, then it will use post_path(#post). If it's not, then posts_path.
The form_for method itself checks to see if the object passed in is persisted also, and if it is then it'll default to a PUT HTTP
method, otherwise a POST.
So this is how form_for can be flexible enough to have an identical syntax on both a new and edit view. It's becoming more and
more common these days for people to even put their whole form_for tags into a single partial and include it in both the new and
edit pages.
A more complex form
So form_for is fairly simple for when you pass a normal object, but what happens if you pass an array of objects? Like this, for
instance:
<%= form_for [#post, #comment] do |f| %>
Well, both url_for and form_for have you covered there too.
The url_for method detects that this is an array and separates out each part and inspects them individually. First, what is this
#post thing? Well, in this case let's assume it's a Post instance that is persisted and has the id of 1. Second, what is this
#comment object? It's a Comment instance that has not yet been persisted to the database.
What url_for will do here is build up the URL helper method piece by piece by placing each part in an array, joining it into a routing method and then calling that routing method with the necessary arguments.
First, it knows that the #post object is of the Post class and is persisted, therefore the URL helper will begin with post. Second, it knows that the #comment object is of the Comment class and is not persisted, and therefore comments will follow post in the URL helper build. The parts that url_for now knows about are [:post, :comments].
The url_for method combines these individual parts with an underscore, so that it becomes post_comments and then appends _path
to the end of that, resulting in post_comments_path. Then it passes in just the persisted objects to the call to that method, resulting in a call like this:
post_comments_path(#post)
Calling that method results in this:
"/posts/1/comments"
Best part? form_for will still know to use POST if the #comment object is not a persisted object, and PUT if it is. A good
thing to remember is that the form_for is always for the last object specified in the array. The objects prior to it are just its
nesting, nothing more.
The more objects that are added, the more times url_for will do the hard yards and build the path out... although I recommend that
you keep it to just two parts.
A symbolic form
Now that we've covered using an array containing objects for form_for, let's take a look at another common use. An array containing
at least one Symbol object, like this:
<%= form_for [:admin, #post, #comment] do |f| %>
What the url_for method does here is very simple. It sees that there's a Symbol and takes it as it is. The first part of the
url will simply be the same as the symbol: admin. The URL that url_for knows of at this point is just [:admin].
Then url_for goes through the remaining parts of the array. In this case, let's assume both #post and #comment are persisted
and that they have the ids of 1 and 2 respectively. Same classes as before. url_for then adds post to the URL that it's building,
and comment too, resulting in [:admin, :post, :comment].
Then the joining happens, resulting in a method of admin_post_comment_path, and because both #post and #comment are persisted here,
they're passed in, resulting in this method call:
admin_post_comment_path(#post, #comment)
Which (usually) turns into this path:
/admin/posts/1/comments/2
You can use the array form of polymorphic routing with the redirect_to, link_to and form_for methods. There's probably other
methods that I'm not remembering right now that can do it too... it's generally anything in Rails that would normally take a URL.
There's no need to build your URLs in any Rails version greater-than 2 using hashes; that's pretty old school.
Instead, experiment with your new knowledge of polymorphic routing and use it to the best of your advantage.
This should work:
link_to "User Posts", user_posts_path(#user)
for more details visit:
http://guides.rubyonrails.org/routing.html
link_to uses url_for which uses polymorphic_url.
polymorphic_url:
builds the helper method, using the class name of active record objects
calls the helper with the active record objects as arguments
Therefore, as others said, you should use:
link_to 'User Posts', [#user, :posts]
for which the path is:
user_posts_path(#user)
^^^^ ^^^^^ ^^^^^
1 2 3
class of #user because it is an active record
convert to string because symbol
add as call argument because active record
That builds the good helper method.
This is how to link to a nested resource in the latest Rails:
link_to 'Destroy Comment', post_comment_path(comment.post, comment)
Note: This is in a partial so there isn't a #.

Resources