is that a ruby on rails strange behaviour with overwrite params? Or do I just dont aderstand ruby, again? - ruby-on-rails

pre annotation: I have a solution, I want to understand what happens here, and if this behaviour is intended
edit a try for a better readable shortcut:
if you have the following code in Rails Controller:
def get_page
prepare_anythig params
if is_it_monday?
params=monday_default_paramms
end
finish_any_other_thing params
end
this works only on monday
Following functioning little controller function, not very intersting, I know
class SvgTestController < SiteController
def get_the_page
require "base64"
#main_width="auto"
params[:ci]||=['default']
puts "? params:",params
generate_drawing(params, false)
render ...
end
end
the console shows me how expected:
? params:
{"ci"=>"not default", "controller"=>"svg_test", "action"=>"get_the_page"}
Then I made a small (ok, erroneous or not valid as I now know - or think) change, I extended my get_the_page with 'get params via base64 encode json'
class SvgTestController < SiteController
def get_the_page
require "base64"
#main_width="auto"
params[:ci]||=['default']
# add here
puts "? params:",params
json=params[:json]
puts "json?",json.inspect
if json
plain = Base64.decode64(json)
puts "we are in here:", plain
params=JSON.parse(plain).with_indifferent_access
puts "? params now:",params
end
# end
puts "? params:",params
generate_drawing(params, false)
render ...
end
end
Solution working fine and the output like this:
? params:
{"json"=>"eyJjaSI6eyIwMDAwMDAwMDAyMDQ4MDgiOnsic3J2IjoxfX19", "controller"=>"svg_test", "action"=>"get_the_page", "ci"=>["default"]}
json?
"eyJjaSI6eyIwMDAwMDAwMDAyMDQ4MDgiOnsic3J2IjoxfX19"
we are in here:
{"ci":{"000000000204808":{"srv":1}}}
? params now:
{"ci"=>{"000000000204808"=>{"srv"=>1}}}
? params:
{"ci"=>{"000000000204808"=>{"srv"=>1}}}
later I got, working not with JSON-logic
NoMethodError in SvgTestController#get_the_page
undefined method `[]' for nil:NilClass
and my console shows me:
? params:
{"ci"=>"10.203.192.83", "controller"=>"svg_test", "action"=>"get_the_page"}
json?
nil
? params:
_(nothing to read here)_
So ruby overwrites my params (ok its a method, my fault) even if not in if ... end?
Again I ask: Is this wanted? And if, how to prevent such errors without knowing all and all the time about whats behind words like params?
edit
My solution, but not the answer to my question
...
params_used=params
json=params[:json]
if json
plain = Base64.decode64(json)
params_used=JSON.parse(plain).with_indifferent_access
end
puts "? params:",params_used
generate_drawing(params_used, false)

I think the "error" is because you're actually creating a variable. Annotation of your code:
def get_the_page
require "base64"
#main_width="auto"
params[:ci]||=['default'] # params method
# you modified #params, a mutable hash
# add here
puts "? params:",params # params method
json=params[:json] # params method
# you accessed #params[:json]
puts "json?",json.inspect
if json
plain = Base64.decode64(json)
puts "we are in here:", plain
params=JSON.parse(plain).with_indifferent_access # params variable
puts "? params now:",params # params variable
end
# end
puts "? params:",params # params variable
generate_drawing(params, false) # params variable
render ...
end
What's happening, I'd wager, is that the Ruby interpreter picks up the fact that a variable named params continues to be used after if block, so proceeds to initialize it (to nil) immediately before your if block irrespective of whether the block is visited or not.

Related

Rails merge (and manual assignment) assigning nil

I am trying to create a model in a controller using strong params in Rails 5.1 (some things changed from previous for strong_params). However, when I inspect the params, the merged ones are NOT present and I am getting an ForbiddenAttributesError tracing back to the Model.new line below. The only thing in the Model is verify presence for all the attributes.
class ModelController < ApplicationController
before_action :application_controller_action
def create
#model = Model.new(strong_params)
if #model.valid?
result = #model.save
else
render html: 'MODEL NOT VALID'
end
render html: 'DONE'
end
private
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
params.require(:model).permit(:name, :attribute_1, :attribute_2).merge(attribute_1: #attr_1, attribute_2: #attr_2)
# Inserting the following two lines causes a ForbiddenAttributesError
puts params.inspect # DOES NOT INCLUDE #attr_1 and/or #attr_2
return params
end
I may be doing something wrong though because I've even tried putting the strong params into a model with the attributes (which I can inspect just before) and it still fails because the validation for attr_1 and attr_2 fail in the Model.
def create
puts #user.inspect (not nil)
#model = Model.new(name: strong_params[:name], attribute_1: #attr_1, attribute_2: #attr_2)
UPDATE:
OK, I'm getting some weird errors from my troubleshooting. It seems the merge is not working correctly, though I'm sure it was at one point.
The first thing I checked was #attr_1 and #attr_2, they are definitely getting set.
For troubleshooting purposes, I've reduced the application before_action to this:
def application_before_action
#attr_1 = Model.first
#attr_2 = Model.last
With the code above, inspecting the params object and then returning it after the require().permit(), I am getting a ForbiddenAttributesError (no indication of what). If I remove those lines, I get a missing attributes error from the model indicating that #attr_1 and #attr_2 are missing.
UPDATE 2
Changed the title of the question, because I probably got confused during troubleshooting. I think the issue is just that the merge is assigning nil... but strangely so is the manual assignment suggested by (myself originally) and another answer here. The attributes keys are there, but they're getting assigned nil. Also, noticed my example was using a single Model, when there are actually two Models, Model1 and Model2. I am assigning the values from Model1 to Model2.
Here is a better demonstration of the error:
def create
puts '0:'
puts #model1.inspect
puts '1:'
puts strong_params.inspect
#model2 = Model2.new(strong_params) do |m|
m.user_id = #attr_1
m.account_number = #attr_2
end
puts '3:'
puts #model2.inspect
if #model2.valid?
result = #model2.save
render html: 'SUCCESS' and return
else
render html: #model2.errors.full_messages and return
end
end
Outputs in console:
0:
#<Model1 id: 29, attribute_1: 'test_value_1', attribute_2: 'test_value_2', created_at: "2018-08-15 03:55:08", updated_at: "2018-08-15 04:05:01">
1:
<ActionController::Parameters {"name"=>"test_name", "attribute_1"=>nil, "attribute_2"=>nil} permitted: true>
3:
#<Model2 id: nil, name: 'test_name', attribute_1: nil, attribute_2: nil, created_at: nil, updated_at: nil>
Obviously the nil id and timestamps are because the model has not been saved yet.
The html model2.errors.full_messages are: ["attribute_1 can't be blank", "attribute_2 can't be blank"]
SOLUTION
Coming from a pure ruby environment previously, I was mistaken about ActiveRecord default accessors for models. Removing the accessors seems to have resolved the problem.
Instead of mucking about with the params hash you can just assign the odd values one by one:
class ModelController < ApplicationController
before_action :application_controller_action
def create
#model = Model.new(strong_params) do |m|
m.attribute_1 = #attr_1
m.attribute_2 = #attr_2
end
if #model.valid?
result = #model.save
else
render html: 'MODEL NOT VALID'
end
# don't do this it will just give a double render error
render html: 'DONE'
end
private
private
def strong_params
params.require(:model).permit(:name, :attribute_1, :attribute_2)
end
end
In general this is a much more readable way to merge params with values from the session for example.
The reason your strong parameters method does not work is its just plain broken in every possible way. The main point is that you're not returning the whitelisted and merged params hash. You're returning the whole shebang.
You also seem under the faulty impression that .require, .permit and .merge alter the orginal hash - they don't - they return a new hash (well actually an ActionContoller::Parameters instance to be specific).
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
permitted = params.require(:model).permit(:name, :attribute_1, :attribute_2)
.merge(attribute_1: #attr_1, attribute_2: #attr_2)
puts permitted.inspect
permitted # return is implicit
end
Or just:
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
params.require(:model).permit(:name, :attribute_1, :attribute_2)
.merge(attribute_1: #attr_1, attribute_2: #attr_2)
end
You could convert to hash before merge
params.require(:model).permit(:name).to_h.merge(attribute_1: #attr_1, attribute_2: #attr_2)
You would have to be very sure that you are assigning non-user input though otherwise you are negating the purpose of strong parameters.

How to pass Arguments and use those in (resque-status) Resque::JobWithStatus?

my resque worker class is:
require 'resque'
require 'resque/job_with_status'
class PatstatResqueWorker < Resque::JobWithStatus
#queue = :my_worker_q
def self.perform(query, label)
puts "query:"
puts options['query']
puts "label:"
puts options['label']
end
end
and my controller part, where I call this resque is...
class MyController < ApplicationController
def resque
job_id = PatstatResqueWorker.create(:query => #query, :label => "yes")
status = Resque::Plugins::Status::Hash.get(job_id)
end
end
and its not working :(
if i remove the parameter from resque function it says Wrong number of arguments (2 for 0) and if i add the parameter section back it says options not defined :(
Could you help?
The reason you're getting the "options not defined" error is that you haven't defined options in the method that uses it. Your self.perform method expects to receive two distinct arguments, query and label, but the code inside the method expects to have an options hash. You've got to choose one or the other.
Either do this:
def self.perform(query, label)
# use the parameters we've already defined
puts "query:"
puts query
puts "label:"
puts label
end
# call it like this
PatstatResqueWorker.create(#query, "yes")
Or else do this:
# change the method signature to match what you're doing
def self.perform(options)
puts "query:"
puts options['query']
puts "label:"
puts options['label']
end
# call it like this, with string keys
PatstatResqueWorker.create('query' => #query, 'label' => "yes")
Notice that with the hash version, I changed the call to use strings for the hash keys instead of symbols. You can use symbols if you want, but you'd have to change it in the body of the method as well (i.e. options[:query] instead of options['query']). You've just got to be consistent.

Is there a way to access method arguments in Ruby?

New to Ruby and ROR and loving it each day, so here is my question since I have not idea how to google it (and I have tried :) )
we have method
def foo(first_name, last_name, age, sex, is_plumber)
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{SOMETHING}"
end
So what I am looking for way to get all arguments passed to method, without listing each one. Since this is Ruby I assume there is a way :) if it was java I would just list them :)
Output would be:
Method has failed, here are all method arguments {"Mario", "Super", 40, true, true}
In Ruby 1.9.2 and later you can use the parameters method on a method to get the list of parameters for that method. This will return a list of pairs indicating the name of the parameter and whether it is required.
e.g.
If you do
def foo(x, y)
end
then
method(:foo).parameters # => [[:req, :x], [:req, :y]]
You can use the special variable __method__ to get the name of the current method. So within a method the names of its parameters can be obtained via
args = method(__method__).parameters.map { |arg| arg[1].to_s }
You could then display the name and value of each parameter with
logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ')
Note: since this answer was originally written, in current versions of Ruby eval can no longer be called with a symbol. To address this, an explicit to_s has been added when building the list of parameter names i.e. parameters.map { |arg| arg[1].to_s }
Since Ruby 2.1 you can use binding.local_variable_get to read value of any local variable, including method parameters (arguments). Thanks to that you can improve the accepted answer to avoid evil eval.
def foo(x, y)
method(__method__).parameters.map do |_, name|
binding.local_variable_get(name)
end
end
foo(1, 2) # => 1, 2
One way to handle this is:
def foo(*args)
first_name, last_name, age, sex, is_plumber = *args
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{args.inspect}"
end
This is an interesting question. Maybe using local_variables? But there must be a way other than using eval. I'm looking in Kernel doc
class Test
def method(first, last)
local_variables.each do |var|
puts eval var.to_s
end
end
end
Test.new().method("aaa", 1) # outputs "aaa", 1
If you need arguments as a Hash, and you don't want to pollute method's body with tricky extraction of parameters, use this:
def mymethod(firstarg, kw_arg1:, kw_arg2: :default)
args = MethodArguments.(binding) # All arguments are in `args` hash now
...
end
Just add this class to your project:
class MethodArguments
def self.call(ext_binding)
raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding)
method_name = ext_binding.eval("__method__")
ext_binding.receiver.method(method_name).parameters.map do |_, name|
[name, ext_binding.local_variable_get(name)]
end.to_h
end
end
This may be helpful...
def foo(x, y)
args(binding)
end
def args(callers_binding)
callers_name = caller[0][/`.*'/][1..-2]
parameters = method(callers_name).parameters
parameters.map { |_, arg_name|
callers_binding.local_variable_get(arg_name)
}
end
You can define a constant such as:
ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge"
And use it in your code like:
args = eval(ARGS_TO_HASH)
another_method_that_takes_the_same_arguments(**args)
If the function is inside some class then you can do something like this:
class Car
def drive(speed)
end
end
car = Car.new
method = car.method(:drive)
p method.parameters #=> [[:req, :speed]]
If you would change the method signature, you can do something like this:
def foo(*args)
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{args}"
end
Or:
def foo(opts={})
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{opts.values}"
end
In this case, interpolated args or opts.values will be an array, but you can join if on comma. Cheers
It seems like what this question is trying to accomplish could be done with a gem I just released, https://github.com/ericbeland/exception_details. It will list local variables and vlaues (and instance variables) from rescued exceptions. Might be worth a look...
Before I go further, you're passing too many arguments into foo. It looks like all of those arguments are attributes on a Model, correct? You should really be passing the object itself. End of speech.
You could use a "splat" argument. It shoves everything into an array. It would look like:
def foo(*bar)
...
log.error "Error with arguments #{bar.joins(', ')}"
end

Rails Method Ignoring Default Param - WHY?

I am at a loss as to why this is happening. I have the following function:
def as_json(options = {})
json = {
:id => id,
# ... more unimportant code
}
unless options[:simple]
# ... more unimportant code
end
json
end
It works most of the time, but in one particular partial where I call this:
window.JSONdata = <%= #day.to_json.html_safe %>
I get the following error:
ActionView::Template::Error (You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]):
Pointing to the line "unless options[:simple]". As far as I can tell, the options hash is nil - thus the method is ignoring the default param assignment. WHY? I can fix this by changing the method to:
def as_json(options)
options ||= {}
json = {
:id => id,
# ... more unimportant code
}
unless options[:simple]
# ... more unimportant code
end
json
end
Does this make any sense to anyone!? Most appreciative for your help.
This is because you're using to_json, which has a default options of nil. to_json will eventually call as_json and pass the nil as options.
Here's where it happens on the Rails source code. First, to_json is defined with the default options of nil.
# https://github.com/rails/rails/blob/v3.0.7/activesupport/lib/active_support/core_ext/object/to_json.rb#L15
def to_json(options = nil)
ActiveSupport::JSON.encode(self, options)
end
Eventually it will arrive here.
# https://github.com/rails/rails/blob/v3.0.7/activesupport/lib/active_support/json/encoding.rb#L41
def encode(value, use_options = true)
check_for_circular_references(value) do
jsonified = use_options ? value.as_json(options_for(value)) : value.as_json
jsonified.encode_json(self)
end
end
As you see, as_json is called with value.as_json(options_for(value)) and options_for(value) will return the default value of to_json, which is nil.

find untranslated locales in rails

I'm using rails 2.3.5 with i18n. I's there a way to find all not yet translated locales in all views?
Maybe a after_filter in the application controller, but which code I can use for this job?
thanks
When using the i18n gem (which Rails does), you can specify your own exception handler. Try this code:
# A simple exception handler that behaves like the default exception handler
# but additionally logs missing translations to a given log.
#
module I18n
class << self
def missing_translations_logger
##missing_translations_logger ||= Logger.new("#{RAILS_ROOT}/log/missing_translations.log")
end
def missing_translations_log_handler(exception, locale, key, options)
if MissingTranslationData === exception # use MissingTranslation in Rails 3.x !!!
puts "logging #{exception.message}"
missing_translations_logger.warn(exception.message)
return exception.message
else
raise exception
end
end
end
end
I18n.exception_handler = :missing_translations_log_handler
(put it for example into RAILS_ROOT/config/initializers/i18n.rb)
Now, whenever you try to translate a key for which you have no translation specified, a warning gets printed into RAILS_ROOT/log/missing_translations.log.
Hope this helps!
I couldn't find a simple trick to do this, so I did this. First implement a 'before_filter' in your application_controller.rb
before_filter :set_user_language
# set the language, 'zen' is a special URL parameter that makes localizations the use the 't' method visible
def set_user_language
# turn on 'zen' to see localization by adding 'zen=true' to query string, will stay on until a query with 'zen=false'
session[:zen] = (session[:zen] || params[:zen] == "true") && params[:zen] != "false"
I18n.locale = 'en'
end
The above finds 'zen=true' and 'zen=false' in the query string. Then add this method to your application_helper.rb:
def t(*args)
result = super(*args)
result = "[#{result}]" if session[:zen] && result.is_a?(String)
result
end
With this method 'zen=true' makes the 't' method display localized strings in square brackets []. To turn it off enter a query string with 'zen=false'.

Resources