I am unfortunately running an application on Rails 2.3.18 and am seeing strange behavior with the params variable within the controllers. There are some areas of the app that (for some reason) assign params to itself or an empty hash by default.
params = (params || {})
Now the params variable is initialized to be the request parameters so it should evaluate to true in a logical expression. However, in this case params gets set to {}. Why exactly is this happening?
I don't have a Rails 2.3 app to play around with but params in a controller is actually method but saying params = ... creates a local variable that shadows the standard params method. For example, try this in irb:
def x
puts 'x is being called'
{ :where_is => 'pancakes house?' }
end
x = x || { }
and you'll see that x is { } and the x method never even gets called. The code that says:
params = (params || {})
is effectively doing this:
params = nil # Declare a local variable and initialize it to nil
params = params || { }
# -------^^^^^^ the local variable above, not the method
However, if you force the RHS params to be a method call:
params = params() || { }
# -------------^^
then it should call the method but you'll still have the local params variable on the LHS of the assignment.
I think you need to figure out why the code is doing params = params || { } and refactor it so that it isn't necessary. Are these controller methods trying to work when they're not actually in a controller? Are they depending on dodgy edge cases in older versions of Ruby? Was that code added cargo-cult style by someone copying mystery code from a blog they didn't understand?
Related
Documentation: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-dig
I run rails c and write this:
params = ActionController::Parameters.new(foo: { bar: { baz: 1 } })
And further:
params.dig(:foo, :bar, :baz)
These two lines I took from the page, the link to which is given above.
As a result, I get nil.
When working with Hash everything is fine. There is a problem when working with ActionController::Parameters. I have this problem inside the Rails application.
Need your opinion on this.
ActionController::Parameters is a subclass of Hash, you can convert it directly to a hash using the to_h method on the params hash.
However to_h only will work with whitelisted params, so you can do something like:
params = ActionController::Parameters.new(foo: { bar: { baz: 1 } })
params.permit!
params.to_h.dig(:foo, :bar, :baz)
But if instead you do not want to whitelist then you just need to use the to_unsafe_h method.
I'm implementing authorization in an app according to this Railscasts episode.
In one of the instance methods, Ryan Bates is using the method .call and I don't understand what exactly it is doing. This is the code for the whole model. And this is the particular method:
def allow?(controller, action, resource = nil)
allowed = #allow_all || #allowed_actions[[controller.to_s, action.to_s]]
allowed && (allowed == true || resource && allowed.call(resource))
end
The resource argument is an instance object and the local variable allowed is supposed to be a boolean value.
call evaluates the proc or method receiver passing its arguments to it.
pr = ->a{a.map(&:upcase)}
pr.call(%w[hello world])
# => ["HELLO", "WORLD"]
m = "hello".method(:concat)
m.call(" world")
# => "hello world"
It is used to call back a piece of code that has been passed around as an object.
I am trying to assign a default value to a check box in ROR. The following is the heirachy:
Check if value is in the params (url querystring)
Check if it's in the session variable
If neither, default to all possible values and set #rates to all possible values
I have written the following code:
#all_rates = Rates.all_rates
rates_all = {}
#all_rates.each {|rate| rates_all[rate] = "1"}
p rates_all
#rates = params[:rates] ||= session[:rates] ||= rates_all
puts #rates.length, #rates
when i p rates_all, i get the hash back, however when i check #rates.length it is not being assigned i get a 0.
Did you check if params[:rates] is nil? Because if it's an empty hash then it's still an object, just without any values. But the empty hash object would be assigned to #rates anyway, resulting that rates is an empty hash object too, with length 0.
Give this a try:
#rates = case
when params[:rates].present?
params[:rates]
when session[:rates].present?
session[:rates]
else
Rates.all_rates.inject({}) { |hsh, rate| hsh.merge(rate => '1') }
end
Sidebar:
Your model name should be the singular Rate. The ||= syntax in your example is invalid. It should be just ||.
Hi
I want to use two params hashes in one page
The job of this page is straightforward, it's an edit page, and I want it to send out notifications to a server once the editing job is done.
def update
#description = Tempdescription.find(params[:id])
#description.update_attributes(params[:tempdescription])
sendnotification
end
def sendnotification
params[:to_ids]="xxxx"
sig = hash_params(params);
params[:sig] = sig
response = RestClient.post "http://api.xxxx.com/restserver.do", params, :content_type => :json, :accept => :json
render :text=>response
end
def hash_params(params)
params = Hash[*params.sort.flatten]
payload = ''
params.sort.each do |pair|
key, value = pair
payload = payload + "#{key}=#{value}"
end
return Digest::MD5.hexdigest(payload + API_SECRET)
end
Not surprisingly the params in sendnotification also includes params used for updating
and the server returns 104 error
Therefore,
I tried
new_params=Hash[]
and use new_params to replace the old params in sendnotification
But then rails complains
undefined method `<=>' for :session_key:Symbol
app/controllers/tempdescriptions_controller.rb:72:in `<=>'
app/controllers/tempdescriptions_controller.rb:72:in `sort'
app/controllers/tempdescriptions_controller.rb:72:in `hash_params'
app/controllers/tempdescriptions_controller.rb:45:in `sendnotification'
So I am thinking if there is any way I can create another params?
Thanks in advance
Ok, having complained about your formatting I suppose I should hazard an attempt at your problem.
This code:
def hash_params(params)
params = Hash[*params.sort.flatten]
payload = ''
params.sort.each do |pair|
key, value = pair
payload = payload + "#{key}=#{value}"
end
return Digest::MD5.hexdigest(payload + API_SECRET)
end
.. appears to accept a hash as its argument and then recreate it with the keys sorted. Presumably this code is targeted at ruby 1.9 otherwise that would be rather pointless. It then sorts again for no reason I can determine before joining the keys and values with = but without separating the pairs with &.
The error is a little mysterious though; I have no trouble sorting symbols with ruby 1.9. Perhaps you're running ruby 1.8?
Ok...after playing with rails console for a while I finally find a solution to this problem.
In sendnotification method I created a new hash
p=Hash[]
but simply putting this will not work, as I mentioned before.
Then I changed all
p[:key]
to
p["key"]
and it works.
Obviously Hash#sort doesn't work with hash[:key] if the hash is newly created but it works with params and that's what puzzled me and made me believe there is a difference between params and normal hash.
I am using Ruby 1.8.7 so I think it might just be a bug of this version.
When I assign in my controller
#my_hash = { :my_key => :my_value }
and test that controller by doing
get 'index'
assigns(:my_hash).should == { :my_key => :my_value }
then I get the following error message:
expected: {:my_key=>:my_value},
got: {"my_key"=>:my_value} (using ==)
Why does this automatic symbol to string conversion happen? Why does it affect the key of the hash?
It may end up as a HashWithIndifferentAccess if Rails somehow gets ahold of it, and that uses string keys internally. You might want to verify the class is the same:
assert_equal Hash, assigns(:my_hash).class
Parameters are always processed as the indifferent access kind of hash so you can retrieve using either string or symbol. If you're assigning this to your params hash on the get or post call, or you might be getting converted.
Another thing you can do is freeze it and see if anyone attempts to modify it because that should throw an exception:
#my_hash = { :my_key => :my_value }.freeze
You might try calling "stringify_keys":
assigns(:my_hash).should == { :my_key => :my_value }.stringify_keys
AHA! This is happening not because of Rails, per se, but because of Rspec.
I had the same problem testing the value of a Hashie::Mash in a controller spec (but it applies to anything that quacks like a Hash)
Specifically, in a controller spec, when you call assigns to access the instance variables set in the controller action, it's not returning exactly the instance variable you set, but rather, a copy of the variable that Rspec stores as a member of a HashWithIndifferentAccess (containing all the assigned instance variables). Unfortunately, when you stick a Hash (or anything that inherits from Hash) into a HashWithIndifferentAccess, it is automatically converted to an instance of that same, oh-so-convenient but not-quite-accurate class :)
The easiest work-around is to avoid the conversion by accessing the variable directly, before it's converted "for your convenience", using: controller.view_assigns['variable_name'] (note: the key here must be a string, not a symbol)
So the test in the original post should pass if it were changed to:
get 'index'
controller.view_assigns['my_hash'].should == { :my_key => :my_value }
(of course, .should is no longer supported in new versions of RSpec, but just for comparison I kept it the same)
See this article for further explanation:
http://ryanogles.by/rails/hashie/rspec/testing/2012/12/26/rails-controller-specs-dont-always-play-nice-with-hashie.html
I know this is old, but if you are upgrading from Rails-3 to 4, your controller tests may still have places where Hash with symbol keys was used but compared with the stringified version, just to prevent the wrong expectation.
Rails-4 has fixed this issue: https://github.com/rails/rails/pull/5082 .
I suggest updating your tests to have expectations against the actual keys.
In Rails-3 the assigns method converts your #my_hash to HashWithIndifferentAccess that stringifies all the keys -
def assigns(key = nil)
assigns = #controller.view_assigns.with_indifferent_access
key.nil? ? assigns : assigns[key]
end
https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L10
Rails-4 updated it to return the original keys -
def assigns(key = nil)
assigns = {}.with_indifferent_access
#controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
key.nil? ? assigns : assigns[key]
end
https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L11
You can also pass your Hash object to the initializer of HashWithIndifferentAccess.
You can use HashWithIndifferentAccess.new as Hash init:
Thor::CoreExt::HashWithIndifferentAccess.new( to: 'mail#somehost.com', from: 'from#host.com')