In Ruby why won't `foo = true unless defined?(foo)` make the assignment?
Apparently, ruby creates local variable at parse time setting them to nil
so it is defined and this is done whether the code is executed or not.
When the code is evaluated at your first line, it doesn't execute the assignment part since foo
is set to nil
. In the second line, because fooo
has not been parsed yet, defined?
returns nil
letting the code inside the block execute and assign fooo
.
As an example you can try this:
if false
foo = 43
end
defined? foo
=> "local-variable"
This is taken from a forum post at ruby-forum.
Ruby's foo = true if !defined? foo won't work as expected
Ruby defines a local variable just before executing a line containing an assignment, so defined?(foo)
will always be true
for the one-liner.
Another example showing that local variables are defined before any part of the line are executed:
defined? foo # => false
foo = foo # => foo is now nil
if/unless modifiers vs. and/or
Alternatives that I would choose (and have used) when this issue arises:
if x=a[2]
n = 3*(x**2) + 4*x + 5
end
if x=a[2] then n = 3*(x**2) + 4*x + 5 end
x=nil
n = 3*(x**2) + 4*x + 5 if x=a[2]
unless defined? is not working in my code
defined?
method will return:
nil => expression not recognizable
The problem in the above snippet is the scope of the local variable. Its end on the line where you using it
. To learn more about local variable, please check this: local_variable
pry(main)> p "local_var is not initialized" unless defined? local_var
=> "loca_var is not initialized"
but if you do this:
pry(main)> local_var = "initialized" unless defined? local_var
=> nil
local_var is still nil
because its scoped end after that line, so whatever assigned were wasted.
Solution: I will suggest if you want this behaviour then use this one:
local_var ||= "initialized"
How to write a switch statement in Ruby
Ruby uses the case
expression instead.
case x
when 1..5
"It's between 1 and 5"
when 6
"It's 6"
when "foo", "bar"
"It's either foo or bar"
when String
"You passed a string"
else
"You gave me #{x} -- I have no idea what to do with that."
end
Ruby compares the object in the when
clause with the object in the case
clause using the ===
operator. For example, 1..5 === x
, and not x === 1..5
.
This allows for sophisticated when
clauses as seen above. Ranges, classes and all sorts of things can be tested for rather than just equality.
Unlike switch
statements in many other languages, Ruby’s case
does not have fall-through, so there is no need to end each when
with a break
. You can also specify multiple matches in a single when
clause like when "foo", "bar"
.
Ruby syntax oddity
It's due to the parsing of lines, vs execution time. In the first version, value is parsed and set and then the puts evaluated. In the second line, when the parser gets to the variable puts value
, it has not yet been defined. In other words, it can't run the line to set the variable, until it has first parsed the line.
In Ruby, how do I check if method foo=() is defined?
The problem is that the foo=
method is designed to be used in assignments. You can use defined?
in the following way to see what's going on:
defined?(self.foo=())
#=> nil
defined?(self.foo = "bar")
#=> nil
def foo=(bar)
end
defined?(self.foo=())
#=> "assignment"
defined?(self.foo = "bar")
#=> "assignment"
Compare that to:
def foo
end
defined?(foo)
#=> "method"
To test if the foo=
method is defined, you should use respond_to?
instead:
respond_to?(:foo=)
#=> false
def foo=(bar)
end
respond_to?(:foo=)
#=> true
Inadvertent use of = instead of ==
Most of the time, compilers try very hard to remain backward compatible.
Changing their behavior in this matter to throw errors will break existing legitimate code, and even starting to throw warnings about it will cause problems with automatic systems that keep track of code by automatically compiling it and checking for errors and warnings.
This is an evil we're pretty much stuck with at the moment, but there are ways to circumvent and reduce the dangers of it.
Example:
void *ptr = calloc(1, sizeof(array));
if (NULL = ptr) {
// Some error
}
This causes a compilation error.
In Ruby on Rails, passing locals to a view, defaulting to true, won't work with ||=
More generally, foo ||= default_value
never works if a valid value for foo is false.
foo ||= default_value
is only a valid pattern when all valid values are interpreted as boolean TRUE.
Your statement of do_more == true if !defined? do_more
should use the assignment operator, I presume a typo: do_more = true if !defined? do_more
Maybe better would be do_more = true unless defined? do_more
That looks ok to me, but you need to test to ensure that it works correctly.
If your param is coming in from an HTML form, then the undefined case is actually a zero length string, "". If that is your situation, then you'd want:
do_more = true if do_more == ''
I'd also suggest a comment # set default
Php guy confused about simple assignment in Ruby
The idiomatic ruby version for this would be to write:
is_approved ||= false
which would set is_approved
to false
if is_approved
is falsey
: that means nil
or false
. Since setting to false if false is idempotent, it is not wrong.
Otherwise you could write:
is_approved = false unless is_approved.present?
which is identical to what you wrote:
is_approved = false if is_approved.nil?
but I find it slightly more readable.
So yes: that is also the right way to do it.
You will notice that in ruby there are many ways to achieve the same thing. This is the part of programmer happiness: you choose which way suits you the best, and is most expressive at that place (because sometimes one is better suited, and sometimes the other). But for beginners it is sometimes confusing :)
Related Topics
How to Remove the Bom from a Utf-8 Encoded File
How to Sort an Array in Ruby to a Particular Order
Converting String from Snake_Case to Camelcase in Ruby
When to Use Each Method of Launching a Subprocess in Ruby
How to Edit or Write on Existing Pdf With Ruby
Matching Balanced Parenthesis in Ruby Using Recursive Regular Expressions Like Perl
Cannot Use Rvm-Installed Ruby With Sudo
Ruby Local Variable Is Undefined
Why Not Use Shared Activerecord Connections For Rspec + Selenium
Devise Custom Routes and Login Pages
How to Stub Things in Minitest
How to Define Action With Simple Form For
How to Count Duplicates in Ruby Arrays
In Ruby, Is There an Array Method That Combines 'Select' and 'Map'