Rails params, params.merge error - ruby-on-rails

In order to keep params filter and filter_type through a ajax form submit, I pass them in as hidden fields in my article form which as a result gives me this params hash:
{"utf8"=>"✓", "_method"=>"patch", "article"=>{"filter"=>"xxx", "filter_type"=>"xxxx", ....actual fields of the model that got updated...}, "commit"=>"Update Article", "controller"=>"articles", "action"=>"update", "id"=>"xxx"}
Which means I can access them through params[:article]["filter"].
When I, in my controller's update method, call params.merge(filter: params[:article]["filter"]) nothing gets appended. When I try params = params.merge(filter: params[:article]["filter"]) I get this error NoMethodError (undefined method '[]' for nil:NilClass):
and here comes the weird part: When I do #foo = params.merge(filter: params[:article]["filter"]) I also don't get anything added until I actually output #foo in the view. As soon as I have the <%= #foo %> in my view, the params get properly merged. Can somebody explain why that is?

I think it is better for you to not pass them as articles child. Instead of using f.hidden_field :filter you could use hidden_field_tag :filter, so you will receive the params like: { filter: "filter", article: {}}. This way there is no need to merge.

Related

Bizarrely unable to access elements in an ActionController::Parameters

Making an extension to an already-written rails project. In an erb file I have:
<%= form_for #uploaded_planners[0], :url => method_path, :html => {
:multipart => true } do |form| %>
<%= form.file_field :pic %>
<%=form.submit 'Upload'%>
<% end %>
In the appropriate controller I have:
def create
p params
print "\n"
testing = params[:uploaded_planner]
print testing.class
print "\n"
print testing
print "\n"
print testing['#original_filename']
print testing[:original_filename]
print "\n"
end
and:
def plan
#uploaded_planners = UploadedPlanner.all #there are two i inserted via rails console..
end
Output of those prints are
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"UxgdziS9PwY/SAaOMj4upXzSTf5brOT4R9x+paMNNlxehYwY34OT7hvdGJXwvK/cffPIjYOwdf1h+MndJh6LBg==", "uploaded_planner"=>{"pic"=>#<ActionDispatch::Http::UploadedFile:0x007f9b9b3bc5e8 #tempfile=#<Tempfile:/var/folders/hc/67y08p7s3ws6rsjl5rlxk3f00000gn/T/RackMultipart20170801-25372-godqfg.xlsx>, #original_filename="AF_CSX_DISTRIBUTE_NUMBERS.xlsx", #content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", #headers="Content-Disposition: form-data; name=\"uploaded_planner[pic]\"; filename=\"AF_CSX_DISTRIBUTE_NUMBERS.xlsx\"\r\nContent-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n">}, "commit"=>"Upload", "controller"=>"viacom", "action"=>"create"}
ActionController::Parameters
{"pic"=>#<ActionDispatch::Http::UploadedFile:0x007f9b9d529190 #tempfile=#<Tempfile:/var/folders/hc/67y08p7s3ws6rsjl5rlxk3f00000gn/T/RackMultipart20170801-25372-baavaj.xlsx>, #original_filename="AF_CSX_DISTRIBUTE_NUMBERS.xlsx", #content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", #headers="Content-Disposition: form-data; name=\"uploaded_planner[pic]\"; filename=\"AF_CSX_DISTRIBUTE_NUMBERS.xlsx\"\r\nContent-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n">}
The bottom two print statements are nil. My statements do not dig into the double-nested hash and retrieve the values I am trying to get. I tried every combination syntax-wise that I know of to try to get to those values out of the hash, but to no avail - every time I get nothing. I know the 'create' method in my controller is being invoked - it's how I am getting this print output - it's just I cannot get any value in the hash of "uploaded_planner" (aka can't index into the testing variable). I cannot figure out how to reference those members by any means, which sucks, as those are the members I really want to get to.
Here is the official ActionController doc:
http://api.rubyonrails.org/v4.2/classes/ActionController/Parameters.html#method-i-extract-21
It alleges that I can do:
params[:key]
And I can... the first time. But after that, the object that I have is still an ActionController::Parameters (as shown by my .class print statement) yet I cannot do that very same indexing to get members of the second hash. It is extremely frustrating because I see the data that I need in the print output, but cannot get that data and manipulate it (without going super jank and reading print output somehow... not a good way to do things). Any ideas on this?
params[:uploaded_planner][:pic].original_filename

Parameter ID changes with custom controller

When the form_for is as so:
<%= form_for #reservation, remote: true do |f| %>
I get the following params:
Parameters: {"utf8"=>"✓", "reservation"=>{"party_size"=>"1",
"persons_attributes"=>{"0"=>{"first_name"=>"Big Name",
"last_name"=>"test", "meal_id"=>"1", "id"=>"24"},
"1"=>{"first_name"=>"", "last_name"=>"", "meal_id"=>"1"}},
"address"=>"test", "city"=>"Test", "state"=>"te", "zip"=>"te"},
"commit"=>"submit", "id"=>"24"}
But when I put a custom controller onto the form_for, as so
<%= form_for #reservation, url: {controller: 'static_pages'}, remote: true do |f| %>
I get the following params:
Parameters: {"utf8"=>"✓", "reservation"=>{"party_size"=>"1",
"persons_attributes"=>{"0"=>{"first_name"=>"Big Name",
"last_name"=>"test", "meal_id"=>"1", "id"=>"24"},
"1"=>{"first_name"=>"", "last_name"=>"", "meal_id"=>"1"}},
"address"=>"test", "city"=>"Test", "state"=>"te", "zip"=>"te"},
"commit"=>"submit", "id"=>"index"}
How do I ensure that the params[:id] remains the same? All I need is the standard form_for with a different controller called (the same for is used for both new\ create and edit\ update).
'form_for' with an active record object tries to identify the URL from the object. If it has an ID, it uses the update route, otherwise it uses the create route. Initially it identifies it as something like
/reservation/1
But when you explicitly pass something as the URL it will override it. Now our URL looks like
/reservation/index
I recommend looking into the 'form_for' documentation, but what could really solve your problem would be to explicitly pass the whole URL, instead of just the controller. That way you could get the ID in the right place
In the form you can place the following line of code which will pass the :id to the controller in the POST request.
<%= hidden_field_tag(:id, params[:id]) %>
You might have to add a method to convert it to an integer within the controller.
params[:id].to_i
Depending on how you have your db setup.

Rails Button_to not passing model param

I have the following in my products index page:
<%= button_to "Add", user_order_orderitems_path(user_id: current_user.id, item_id: x.id, order_id: current_user.group.current_order.id), class: "btn btn-mini" %>
Which I can see from the logs is being picked up by my Orderitems#create action in my controller ok. This looks like:
def create
#orderitem = Orderitem.new(orderitem_params)
if #orderitem.save
redirect_to items_path
else
redirect_to items_path
end
end
private
def orderitem_params
params.require(:orderitem).permit(:user_id, :order_id, :item_id)
end
end
The params specified in the button_to call are being created and are showing up in the logs as:
Started POST "/users/1/orders/1/orderitems?item_id=2264" for 127.0.0.1 at 2013-07-03 22:45:24 +0100
Processing by OrderitemsController#create as HTML
Parameters: {"authenticity_token"=>"blah=", "item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}
Fnally - the problem - my strong_params method can't process these params as the three params I care about are not nested in a hash with 'Orderitem's as a key. I would expect, for my create action to work, I need something like:
Parameters: {"authenticity_token"=>"blah=", "orderitems"=>{"item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}}
but I can't for the life of me work out how, with button_to I am able to do this - I have tried a form_for, but this also failed to work. Banging my head on a brick wall for a couple of days on this one...So, how can post my three ids to my OrderItemsController create action from an index view for Products but bypassing any form_for or new actions? Is it possible?
Please let me know if I am approaching this scenario (adding an item to a basket) in completely the wrong way.
This way you can treat a standard hash as one supported by strong parameters
raw_parameters = {"authenticity_token"=>"blah=", "item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}
parameters = ActionController::Parameters.new(raw_parameters)
parameters.permit(:user_id, :order_id, :item_id)

What is a right way to update a boolean field using link_to. Getting undefined method `model_name' for TrueClass:Class

I have the below link_to in a loop in my rails app
<%= link_to 'Up', Product.find(n.id).update_attribute(:opinion, true)%>
But I am unable to update it. Because it gives me the error
undefined method `model_name' for TrueClass:Class
So, I am wondering in this case what is the right way to update a boolean field?
You are not updating the opinion after click the link, you are updating it when the page render.
Lets say your page has only the following code:
<%= link_to 'Up', Product.find(n.id).update_attribute(:opinion, true)%>
What actually happens when you hit that page is that the code wrapped on <%= %> is executed and used to generate an string, that will become your HTML. So while simplifying your expression, in one step of the execution you will have the following code:
<%= link_to 'Up', true %>
And this will generate the HTML
Up
Which is not what you intend of.
Solution
Create an action on your product controller that receives the product id, updates the product and redirects back. So, something like this:
On your route.rb
resource :products do
member do
post 'update_opinion'
end
end
On your products_controller.rb:
def update_opinion
Product.find(params[:id]).update_attribute(:opinion, true)
redirect_to :back
end
On your view:
<%= link_to 'up', update_opinion_project_path(n) %>
Your code does something very different from what you seem to be pursuing.
The helper method link_to accepts two parameters: the link text and the link target, which can be:
a string, like: 'http://stackoverflow.com';
a hash referring all the single components of an internal URL, as defined by routes;
an object, or an array of objects.
As second parameter, you are passing an expression:
Product.find(n.id).update_attribute(:opinion, true)
which is evaluated the first time the code runs, that is, during rendering.
The expression tries to update the attribute immediately and then returns a boolean that says if the operation succeeded or not: true if it successfully updated the attribute, false otherwise.
In your case, it appears to be successfully: it returns true.
Now, true is neighther a string nor an hash, so it is treated as an object, whose class is TrueClass. So it treats it as a model, but not being a model, it does not define the models methods; thus the error.
What you want to do is to put the code in a controller, and put the URL relative to the right controller action as second parameter of link_to.

Rails - escaping backslashes in params causing havoc?

I have a form that passes the same parameters as the form before it:
<%= form_tag({:controller => "workouts", :action => "random"}) do %>
<%= hidden_field_tag :workout, params[:workout] %>
<%= hidden_field_tag :time, params[:time] %>
<%= submit_tag "Get Another", :class => 'btn' %>
The first form works fine, the second form to "get another" gives me the error can't convert Symbol into Integer for this line:
#equipment_ids = params[:workout][:equipment_ids].collect{|s| s.to_i}
The params of the first and second form being passed are:
{"utf8"=>"✓",
"authenticity_token"=>"qj/Q/YWvLKK3A3paAnEom4oTFtq44daX6dvEb8qmgtE=",
"workout"=>{"equipment_ids"=>["",
"508",
"518"]},
"time"=>"25",
"commit"=>"Get Workout"}
{"utf8"=>"✓",
"authenticity_token"=>"qj/Q/YWvLKK3A3paAnEom4oTFtq44daX6dvEb8qmgtE=",
"workout"=>"{\"equipment_ids\"=>[\"\",
\"508\",
\"518\"]}",
"time"=>"25",
"commit"=>"Get Another"}
The only difference is the escaping backslashes. I'm not sure why these would cause a problem?
Changed the hidden field tag to:
<%= hidden_field_tag "workout[equipment_ids][]", params[:workout][:equipment_ids] %>
I just went into the same problem when trying to manually submit a form with a custom POST request. The problem seems to be that net/http post_form method can only handle a single hash where all the values are Strings. If you have hash inside hash (like in the form that scaffold generates), it treats the inner hash as a String, and adds the nasty backslashes that, as you just saw cause havoc :)
The solution for me was to use the lower level "post" method, and to manually encode the hash. Define this module:
module HashToHttpParams
def to_http_params
map do |k, v|
if v.is_a?(Hash)
v.map do |kk, vv|
"#{k}[#{kk}]=#{vv}"
end.join('&')
else
"#{k}=#{v}"
end
end.join('&')
end
end
And then add it to the Hash class in your code:
Hash.send(:include, HashToHttpParams)
Finally encode your params hash before using it. In my code this looks like:
Net::HTTP.start("localhost",3000) do |http|
http.post("/tests", params.to_http_params)
end
Don't know if there's a better solution, but this worked for me.
Source: http://porras.lacoctelera.net/post/2007/10/08/enviando-formularios-con-parametros-compuestos-con-ruby-y-net#c4300080
As Hallucynogenyc pointed out, this is caused by the .post_form (docs) method only wanting a non-nested hash that is strings. I had this same problem, and solved it by switching to use the .post method.
require "net/http"
uri = URI('http://www.yoururl.com')
http = Net::HTTP.new(uri.host)
response = http.post(uri.path, params.to_query)
The .to_query method is also useful for converting hashes.
Another way to solve it is to not use the rails form method to create your params. If you just use straight html, for some reason the .post_form method likes it better.
Email <input name="student_email" type="email" autofocus="true">

Resources