This question already has answers here:
Ruby on Rails: How to pass parameters from view to controller with link_to without parameters showing up in URL
(4 answers)
Closed 7 years ago.
I'm really confused about how rails passes parameters to GET-requests.
I have a rails app with a login form. If a user wants to login with a default test user, he/she can click on a link which passes the test-email and the test-password.
The following works well:
<%= link_to "test login", login_path(:email => "test-email", :password => "test-password") %>
When this is clicked, the login pages loads with the email- and password-field prefilled.
The problem is, that both the email and the password will be shown in the url.
Yesterday, this worked for me:
<%= link_to "test login", login_path(params.except(:email => "test-email", :password => "test-password")) %>
Today, without having changed the code, the parameters are not passed when I use params.except.
So, how can I ensure that the parameters will be passed but not shown in the url?
Edit:
problem solved. see my answer.
I solved it. Added the following to my routes.rb:
get 'test_login' => 'sessions#new', :email => "the test email", :password => "the test password"
So i just can call <%= link_to "test login", test_login_path %>
This will transmit the default email and password and prefill the login form, but the url will just be .../test_login
In my opinion, that's way smarter than params.except.
Related
(This is not about filtered parameters in logfiles.)
I have in my view
<%= password_field_tag :password, '', autofocus: true, class: 'form-control' %>
and then in my controller
#password = params[:password]
In Rails 4 I can use #password to authenticate, all works fine.
In Rails 5, params does NOT contain :password anymore. If I change :password to :password1 in view and controller, all is fine, so somehow :password is filtered out.
I checked for config.filter_parameters (which controles log file filtering) - removed 'password' from it, but it did not influence this behavior.
What am I missing?
May not be the case for the OP but this manifested for me porting an App to Rails 5
This can happen when the parameters are passed into the action at the root level, instead of being scoped: for example for a user registration:
params: { email: "best#buddy.com", password: "passw0rd" }
The Rails (5 at least) parameter wrapper creates 'magic' nested params under the 'user' key (from the controller name) so by the time it reaches your controller action this is actually what is present:
params: { email: "best#buddy.com", password: "passw0rd", user: { email: "best#buddy.com"} }
Notice that the email value was copied underneath the user key! This feels like WTF, but I'm sure there are good reasons to do this. It happens in /actionpack-5.2.2.1/lib/action_controller/metal/params_wrapper.rb#250 during process_action
But given that this is done it may be a bug that the password is not also copied. But this is filtered out by request.filteres_parameters.slice(*wrapped_keys)
So the moral of the story is, nest your form params under the model name.
The reason I assume the OP saw the bug is the use of the password_field_tag which probably creates a :password param outside of the :user namespace key
params: { :password => "passw0rd" }
Instead of the
params: { :user => { :password => "passw0rd" } }
The controller is probably looking for :user..:password (If strong params are being used)
The reason this isn't surfacing as an obvious problem for the rest of the form fields is because the parameter wrapping is copying all the others into params[:user]
Mental Note the filtering of the password is possibly based on the Log filtering configuration?
Rails 3.2.0 | Ruby 1.9.2
Route:
xyz_catalog_device_info
GET|POST /xyz/catalog/devices/:device_id/info(.:format)
xyz/catalog/devices#info`
My app wraps up several API services into one system to provide a single place to maintain SSL Certificates and complicated XML generation logic. My controller makes an HTTP call to a 3rd party API to obtain the information, so there's no XYZ::Catalog::Device persisting (no #device) to implement the usual rails form helper tricks.
I want to create a form like the following:
%form{ :action => xyz_catalog_device_info_path(:format => :xml) }
= label_tag :device_id, "Device ID:"
= text_field_tag :device_id
= submit_tag "Search"
so that it will fill in the device_id in the action.
Is this possible without using javascript? Is there a better way of doing this?
Sounds like what you are looking for is http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html
Where you would just use soemthing like...
= form_tag(xyz_catalog_device_info_path(:format => :xml)) do
= label_tag :device_id, "Device ID:"
= text_field_tag :device_id
= submit_tag "Search"
I've just had Submitting multiple forms in Rails answered which led to another problem. In my form I have the following (there's quite a bit more):
= hidden_field_tag :event_id, :value => #event.id
.control-group
= label_tag :title
.controls
= select(:registration, "registrations[][title]", Registration::TITLE)
and the last line returns:
"registrations"=>[{"title"=>{"registration"=>"Mr"},
as opposed to the expected:
"title"=>"Mr"
I've tried:
= select(:registration, "registrations[][title]", Registration::TITLE)
which returns:
undefined method `registrations[][title]' for #
and also tried:
= select("registrations[][title]", Registration::TITLE)
which returns:
wrong number of arguments (2 for 3)
Look at the parameters below, event(_id) is only there once then the :title oddness starts, any idea what the problem may be?
{"utf8"=>"✓",
"authenticity_token"=>"BQXm5fngW27z/3Wxy9qEzu6D8/g9YQIfBL+mFKVplgE=",
"event_id"=>"7",
"registrations"=>[{"title"=>{"registration"=>"Mr"},
"first_name"=>"Name1",
"last_name"=>"Surname1",
"company_name"=>"Company1",
"designation"=>"Designation1",
"landline"=>"Landline1",
"cell"=>"Cell1",
"email"=>"address1#example.com",
"member"=>{"registration"=>"No"},
"dietary"=>{"registration"=>"None"},
"specify"=>"None"},
{"first_name"=>"Name2",
"last_name"=>"Surname2",
"company_name"=>"Company2",
"designation"=>"Designation2",
"landline"=>"Landline2",
"cell"=>"Cell2",
"email"=>"address2#example.com",
"member"=>{"registration"=>"No"},
"dietary"=>{"registration"=>"None"},
"specify"=>"None",
"title"=>{"registration"=>"Mr"}},
{"first_name"=>"Name3",
"last_name"=>"Surname3",
"company_name"=>"Company3",
"designation"=>"Designation3",
"landline"=>"Landline3",
"cell"=>"Cell3",
"email"=>"address3#example.com",
"member"=>{"registration"=>"No"},
"dietary"=>{"registration"=>"None"},
"specify"=>"None"}],
"commit"=>"Submit registrations"}
Please not that :dietary and :member are formated in the same way as :title. Thanks in advance for your assistance!
EDIT
So submitting to the hash via a text_field_tag is a simple is:
= text_field_tag "registrations[][first_name]"
But the problem comes in with my hidden_field_tag and select_tag.
It's adding bad values, for example:
"title"=>{"registrations"=>"Mr"}
and basically it seems I need to find a better way to add those values into the hash. I'll continue trying to find a solution and will post it here unless someone beats me to it.
Unless i'm reading it wrong, your first two select calls are the same. Have you tried = select(:registrations, "title", Registration::TITLE)? If you look at the documentation of the method in api.rubyonrails.org, it will state that the first value is the object, second is the property. That would be registrations => { :title => "Value" }, in the parameters. If you just want :title => "Value", then you need the select_tag method.
I'm using formtastic to collect information from a form and post dirctly to an external site.
I have no problem generating the form itself. However, since this is being submitted to an external site, they require that each input field have the specific IDs they specify, eg email or last_name -- not the closest Formtastic form, eg _email_input or _last_name_input.
I've looked at the Formtastic v1.2.3 code and I'm 90% sure the answer is "sorry, can't do that." I figured it couldn't hurt to check if I'm missing something. I would like some way to specify the ID completely, as in:
= semantic_form_for('', :url => "https://external_site.com/handler, :method => "post") do |form|
= form.input :last_name, :id => "last_name"
[etc]
Is this possible?
(I will note that I recognize that another, arguably superior approach would be to create an appropriate controller, sanity check the parameters locally, and dispatch the remote call from within the app only when it's well formed; however, that's not what I'm trying to do at the moment.)
Firstly i think you need to use semantic_fields_for for non-model forms. Next, to pass ids to each field, you can use the input_html options to specify them. for eg
form.input :email, :input_html => {:name => 'email', :id => 'email' }
So I've got a user model, with login, email address, password, password confirmation, name, avatar (picture), etc. There are validations on the first 5, basically stating that all 5 need to exist in order to create a new model.
However, this causes problems for me where updates are concerned.
I've got an edit page, where the user can only edit their name and avatar. I'm not currently intending to let them change their login, and I wish to do an email and password change from a different page.
So the edit form looks like this:
<% form_for #user, :html => { :multipart => true } do |u| %>
<p>
<label>Name:</label>
<%= u.text_field :name %>
</p>
<p>
<label>Avatar:</label>
<%= display_user_avatar %>
<%= u.file_field :avatar%>
</p>
<p>
<%= submit_tag %>
</p>
<% end %>
If I attempt to do a #user.update_attributes(params[:user]), then because the only 2 params are name and avatar, the update fails, since stuff like password, password confirmation, email, etc are required to validate the entry, and they simply don't exist in that form.
I can get around this by doing #user.update_attribute(:name, params[:user][:name]), but then I worry about whether avoiding validations is a Good Thing™ or not. Especially with regards to something like password updates, where I do need to validate the new password.
Is there another way?
And if I were to do this simply using update_attribute for :name and :avatar, how would I go about doing it?
Would this work?
params[:user].each do |attribute|
#user.update_attribute(attribute, params[:user][attribute])
end
Is this an acceptable way to do this...?
--edit as follow up --
Okie, I tried as you suggested and did
def update
#user = User.find_by_login(params[:id])
if #user.update_attributes!(params[:user])
redirect_to edit_user_path(#user)
else
flash[:notice] = #user.errors
redirect_to edit_user_path(#user)
end
end
So it's doing the ! version, and the exception caught & displayed in the browser is:
Validation failed: Password is too short (minimum is 5 characters)
The info in the server log is:
Processing UsersController#update (for 127.0.0.1 at 2010-07-18 11:56:59) [PUT]
Parameters: {"user"=>{"name"=>"testeeeeee"}, "commit"=>"Save changes", "action"=>"update", "_method"=>"put", "authenticity_token"=>"BMEGRW/pmIJVs1zlVH2TtZX2TQW8soeCXmMx4kquzMA=", "id"=>"tester", "controller"=>"users"}
Urm. Looking at this, I just realised that it is submitting "id"=>"tester". Now, I have my routes set up so that it is showing the users login name, instead of the user_id... Could that be why? It is attempting to find a update a user with user_id == tester, but since it doesn't exist, it attempts to create one instead?
Is it actually something I'm doing wrong due to the route?
Hmmm... rake routes tells me that the route is:
edit_user GET /users/:id/edit(.:format) {:action=>"edit", :controller=>"users"}
PUT /users/:id(.:format) {:action=>"update", :controller=>"users"}
And I set up the route like that in the user.rb file:
def to_param
"#{login}"
end
but it's definitely been displaying login instead of id all this time. But I'm also doing right at the beginning of the update action, a #user = User.find_by_login(params[:id]), and then updating that #user.
I'm very confused. >.<
Second update:
My User.rb validation stuff are as follows:
validates_length_of :login, :within => 3..20
validates_length_of :password, :within => 5..20
validates_presence_of :login, :email, :password, :password_confirmation, :salt, :name, :on => :create
validates_uniqueness_of :login, :case_sensitive => false
validates_confirmation_of :password
validates_format_of :email, :with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "format is invalid."
attr_accessor :password, :password_confirmation
And the hashed_password section is here:
def password=(pass)
#password = pass
self.salt = User.random_string(10) if !self.salt?
self.hashed_password = User.encrypt(#password, self.salt)
end
u.attributes gives me
>> u.attributes
=> {"salt"=>"NHpH5glxsU", "name"=>"test er", "avatar_updated_at"=>nil, "updated_at"=>Sat Jul 17 07:04:24 UTC 2010, "avatar_file_size"=>nil, "avatar_file_name"=>nil, "hashed_password"=>"84f8675c1ed43ef7f8645a375ea9f867c9a25c83", "id"=>1, "avatar_content_type"=>nil, "login"=>"tester", "email"=>"tester#tester.com", "created_at"=>Fri May 07 10:09:37 UTC 2010}
Urmmm... Ok, so it's what you said, about the virtual attribute password being actually nonexistent...
So how do I get around that?
Bugger, here I thought I was being smart fiddling with my own authentication code...
How easy is it to change to one of those authentication plugins? Will I need to create a new User model? Or should the plugin be able to work with my current one?
Thanks for all the help so far, btw! :D
I've checked this and a partial update of just 2 attributes via update_attributes works fine. All the other attributes are left with their previous values, meaning that the validation shouldn't fail. A couple of things to try:
In your controller action are you loading the user via User.find? i.e. are you starting from a valid model.
Are you sure the update is failing due to validation errors? Try replacing the update_attributes with update_attributes!. The latter will throw an exception if the update fails due to validation. Or check #user.errors after the attempted update to confirm which validation has failed.
Update
If User.find_by_login doesn't find a matching record it will return nil and won't create a new record for you. Is it possible that the tester user in the database has a password that is too short? Maybe that user was created before you put the validations in your code? Are you using any kind of plugin or callback to encrypt user passwords before saving the records? Is password actually a virtual attribute that isn't saved and the actual password is in a field like encrypted_password?
Try this from script/console (use the same environment as you are testing the app with - development or production)
> user = User.find_by_login 'tester'
> user.valid?
> user.attributes
The user.valid? will return true of false and will tell you whether the user is valid to start with, before you even try an update.
Update 2 (fixing the validation)
In terms of fixing your own code, you could add a method like the following to your User model:
def password_validation_required?
hashed_password.blank? || !#password.blank?
end
and then update all your password related validation rules so that they only apply if this method returns true e.g.
validates_length_of :password, :within => 5..20,
:if => :password_validation_required?
What this is saying is only do the password validation rule if we don't yet have a hashed_password (on a new user for example) or if a new plain text password has been specified via password=. If the user already has a password and it is being left unchanged then skip the password validation.
You are right to be considering using a plugin though. Writing your own authentication code can be an interesting excercise and can be required if you have some unusual requirements. The down side is that there can be security issues that you haven't thought of. Retrofitting something like restful_authentication to your app shouldn't be too bad. You might just need to rename one or two fields on your User model.