In Ruby Why Won't 'Foo = True Unless Defined(Foo)' Make the Assignment

In Ruby why won't `foo = true unless defined?(foo)` make the assignment?

Apparently, ruby creates local variable at parse time setting them to nilso 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



Leave a reply



Submit