Here is the pseudo-code of what I want to do:
if #current_user is defined then puts #current_user.name
Use the operator defined? then.
x = 10
defined? x # => "local-variable"
defined? y # => nil
#x = 10
defined? #x # => "instance-variable"
!!defined? x # => true
!!defined? y # => false
write your code as below:
puts #current_user.name if !!defined?(#current_user)
Do you really need to know whether the variable is defined or is it enough to know whether it contains a valid User object?
Instance variables will never raise a NameError, even when they are not defined. They just evaluate to nil, so you can just check for that:
puts #current_user.name unless #current_user.nil?
Since your question is tagged ruby-on-rails, I'll assume that you have ActiveSupport loaded anyway, so you can also use the Object#try extension method:
puts #current_user.try(:name)
puts #current_user.name if instance_variable_defined?(:#current_user)
Related
Is this:
def x?
return #x if #x.present?
#x = #boolean calculation
end
Equivalent to this for boolean values of #x?
def x?
#x ||= #boolean calculation
end
You should not use either option for memoizing boolean values as both will recalculate if #x is false.
present? is a special kind of rails check that equates to !blank? and false.blank? #=> true but even if this was not a boolean check present? and || are not equivalent. For objects that implement empty? blank defers to that so something that is empty? is also blank? and thus is not present?.
"".present? #=> false
"" || true #=> ""
[].present? #=> false
[] || true #=> []
false.present? #=> false
false || true #=> true
#x ||= some_logic equates to #x = #x || some_logic where obviously if #x is false some_logic will fire.
If you just want to see if #x has already been determined to be a value (e.g. not nil) then you could replace this with
def x?
return #x unless #x.nil?
#x = some_logic
end
It depends.
#x ||= value
is equivalent to
#x = #x || value
which assigns value to #x only if #x is falsy. In Ruby, only false and nil are falsy.
Further, #present? is a concept from Rails (see doc).
Note though that depending on the value that you expect to store in #x, it might be equivalent. #present? is simply the negation of #blank? (also a Rails concept). #blank? is defined on Object as follows:
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
Thus, the behavior of the two snippets you posted is guaranteed to be equivalent when #x contains a value that doesn't define its own #present?, #blank?, or #empty? methods.
Now, FalseClass and NilClass both define #blank?:
def blank?
true
end
TrueClass also defines #blank?:
def blank?
false
end
But this is just an optimization as the default implementation from Object would result in the same values.
Therefore, false and nil will return false for #present? and true will return true for #present?.
From this we conclude that, in the specific case of storing boolean values in #x, the behavior of the two snippets is equivalent.
TL;DR: No
Just to add more examples to the answers already above:
def x1?(new_value)
return #x if #x.present?
#x = new_value
end
def x2?(new_value)
#x ||= new_value
end
say #x was a Number:
#x = 123
x1?('new value')
#x
# => 123
#x = 123
x2?('new value')
#x
# => 123
# 123 == 123, so this works for `Number`
but let's say #x was an empty Array:
#x = []
x1?('new value')
#x
# => 'new value'
#x = []
x2?('new value')
#x
# => []
# 'new value' != [], so it doesn't work for empty Array.
^ and there are all other "types" that this doesn't work as well not just empty Array, some of which have already been answered by others here.
In debugging console, while app running (using binding.pry to interrupt it), I can see that my variable Rails.configuration.hardcoded_current_user_key is set:
pry(#<TasksController>)> Rails.configuration.hardcoded_current_user_key
=> "dev"
But it doesn't appear to be defined:
pry(#<TasksController>)> defined?(Rails.configuration.hardcoded_current_user_key)
=> nil
Yet it works fine to store and test its value:
pry(#<TasksController>)> tempVar = Rails.configuration.hardcoded_current_user_key
=> "dev"
pry(#<TasksController>)> defined?(tempVar)
=> "local-variable"
What is going on?
This is because Rails config implements respond_to? but not respond_to_missing?, and defined? only recognizes respond_to_missing?:
class X
def respond_to?(name, include_all = false)
name == :another_secret || super
end
private
def method_missing(name, *args, &block)
case name
when :super_secret
'Bingo!'
when :another_secret
'Nope.'
else
super
end
end
def respond_to_missing?(name, include_all = false)
name == :super_secret || super
end
end
x = X.new
puts x.super_secret # => Bingo!
p defined?(x.super_secret) # => "method"
puts x.another_secret # => Nope.
p defined?(x.another_secret) # => nil
It's recommended to implement respond_to_missing? along with method_missing, I too wonder why Rails did it that way.
You shouldn't be using defined? on anything but the "stub" of that, or in other words, merely this:
defined?(Rails)
Anything beyond that is highly unusual to see, and I'm not even sure it's valid.
defined? is not a method, but a construct that tests if the following thing is defined as a variable, constant or method, among other things. It won't evaluate your code, it will just test it as-is. This means method calls don't happen, and as such, can't be chained.
If you want to test that something is assigned, then you should use this:
Rails.configuration.hardcoded_current_user_key.nil?
In my app a User can create a Business. When they trigger the index action in my BusinessesController I want to check if a Business is related to the current_user.id:
If yes: display the business.
If no: redirect to the new action.
I was trying to use this:
if Business.where(:user_id => current_user.id) == nil
# no business found
end
But it always returns true even when the business doesn't exist...
How can I test if a record exists in my database?
Why your code does not work?
The where method returns an ActiveRecord::Relation object (acts like an array which contains the results of the where), it can be empty but it will never be nil.
Business.where(id: -1)
#=> returns an empty ActiveRecord::Relation ( similar to an array )
Business.where(id: -1).nil? # ( similar to == nil? )
#=> returns false
Business.where(id: -1).empty? # test if the array is empty ( similar to .blank? )
#=> returns true
How to test if at least one record exists?
Option 1: Using .exists?
if Business.exists?(user_id: current_user.id)
# same as Business.where(user_id: current_user.id).exists?
# ...
else
# ...
end
Option 2: Using .present? (or .blank?, the opposite of .present?)
if Business.where(:user_id => current_user.id).present?
# less efficiant than using .exists? (see generated SQL for .exists? vs .present?)
else
# ...
end
Option 3: Variable assignment in the if statement
if business = Business.where(:user_id => current_user.id).first
business.do_some_stuff
else
# do something else
end
This option can be considered a code smell by some linters (Rubocop for example).
Option 3b: Variable assignment
business = Business.where(user_id: current_user.id).first
if business
# ...
else
# ...
end
You can also use .find_by_user_id(current_user.id) instead of .where(...).first
Best option:
If you don't use the Business object(s): Option 1
If you need to use the Business object(s): Option 3
In this case I like to use the exists? method provided by ActiveRecord:
Business.exists? user_id: current_user.id
with 'exists?':
Business.exists? user_id: current_user.id #=> 1 or nil
with 'any?':
Business.where(:user_id => current_user.id).any? #=> true or false
If you use something with .where, be sure to avoid trouble with scopes and better use
.unscoped
Business.unscoped.where(:user_id => current_user.id).any?
ActiveRecord#where will return an ActiveRecord::Relation object (which will never be nil). Try using .empty? on the relation to test if it will return any records.
When you call Business.where(:user_id => current_user.id) you will get an array. This Array may have no objects or one or many objects in it, but it won't be null. Thus the check == nil will never be true.
You can try the following:
if Business.where(:user_id => current_user.id).count == 0
So you check the number of elements in the array and compare them to zero.
or you can try:
if Business.find_by_user_id(current_user.id).nil?
this will return one or nil.
business = Business.where(:user_id => current_user.id).first
if business.nil?
# no business found
else
# business.ceo = "me"
end
I would do it this way if you needed an instance variable of the object to work with:
if #business = Business.where(:user_id => current_user.id).first
#Do stuff
else
#Do stuff
end
Something new to try (:
Assign a variable or return
return unless #business = Business.where(user_id: current_user.id).first
Method would exit at this point if there are no businesses found with current user's ID, or assigns instance variable #business to the first business object.
Tried the following
def pkill(process_name, *host)
puts host.nil? # => false
puts host.empty? # => true
puts host # => nil
puts host[0].nil? # => true
end
Now if I call send("pkill", blah, nil), how/why is host false??
Pretty new to ruby, so keep it smooth :)
Because it's not nil, it's empty array.
def pkill(process_name, *host)
host.nil? # => false
host # => []
host.empty? # => true
end
pkill 'blah'
Are you just trying to make the host parameter optional? The intended use of *host is if you have a variable number of parameters. For example, you method could be called like pkill('blah'), or pkill('blah', 'bloo') or pkill('blah', 'bloo', 'bar'), etc.
If you are just trying to say that host isn't a required argument, you should give it a default value. For example, your method could become
def pkill(process_name, host=nil)
puts host.nil?
puts host
end
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