51

In Ruby, how do you set a variable to a certain value if it is not already defined, and leave the current value if it is already defined?

jrdioko
  • 32,230
  • 28
  • 81
  • 120

4 Answers4

56

While x ||= value is a way to say "if x contains a falsey value, including nil (which is implicit in this construct if x is not defined because it appears on the left hand side of the assignment), assign value to x", it does just that.

It is roughly equivalent to the following. (However, x ||= value will not throw a NameError like this code may and it will always assign a value to x as this code does not -- the point is to see x ||= value works the same for any falsey value in x, including the "default" nil value):

if !x
  x = value
end  

To see if the variable has truly not been assigned a value, use the defined? method:

>> defined? z
=> nil                                                                  
>> z = nil                                                              
=> nil                                                                  
>> defined? z                                                           
=> "local-variable"                                                     
>> defined? @z                                                          
=> nil                                                                  
>> @z = nil                                                             
=> nil                                                                  
>> defined? @z                                                          
=> "instance-variable" 

However, in almost every case, using defined? is code smell. Be careful with power. Do the sensible thing: give variables values before trying to use them :)

Happy coding.

  • What is it about `||=` that makes it not throw a `NameError` if the variable is not defined? Is that built into the operator? Are there other operators that work that way? – jrdioko Jul 12 '11 at 21:54
  • 2
    @jrdioko "Magic" ;) Actually, it has to do with `x` appearing on the Left Hand Side of the assignment -- it doesn't matter if a value has not been assigned yet (it will "default" to `nil`). For instance, `z = z.nil? # => true` even when `z` was not currently defined. –  Jul 12 '11 at 21:57
  • @pst I guess the "defined?" method is just a more elegant alternative than fishing out the "NameError: undefined local variable or method" So the real answer is that you will know if you haven't already defined a variable when your program errors out. – Dmitri Jul 12 '11 at 22:03
  • yeah, this is much better if you are setting variables as true/false – thenengah Jul 12 '11 at 22:06
  • @Dmitri @Codeglot Indeed. The most elegant way is to write code *that can't cause* a `NameError`! (and verified as such with good test coverage) ;-) –  Jul 12 '11 at 22:07
  • Why is using `defined?` code smell? Could you please explain that part. – Hengjie Jan 25 '13 at 09:59
  • @Hengjie Because there should be no question of *if* a `variable` has been set (which implies `defined?` is truthy) in code. Of course, there are exceptions (with `@variable`, perhaps?) .. but if you're at such a special scenario then you'll know it, and why it's okay. If you don't know why it's okay, then it's not :) –  Jan 25 '13 at 16:46
  • @Hengjie That is, just like `eval`, `defined?` is "usually not the right tool": when it appears the usage must be questioned. If the particular usage cannot be suitably defended then it should be removed. –  Jan 25 '13 at 16:53
  • Code smell? More like removing code fragility, just because **you** don't think a situation couldn't occur doesn't mean that it could never happen. Future development may somehow accidentally make this variable undefined (mistaken spelling in the declaration, for example). Checking if a variable is defined will catch such a bug, and make it easy to find. However, without the check, the problem might not be as quick to solve. – SSH This May 10 '13 at 21:50
33
@variable ||= "set value if not set"

So false variables will get overridden

> @test = true 
 => true 
> @test ||= "test"
 => true 
> @test 
 => nil 
> @test ||= "test"
 => "test" 
> @test = false 
 => false 
> @test ||= "test"
 => "test" 
thenengah
  • 42,557
  • 33
  • 113
  • 157
  • 4
    It could be set to a falsey-value, however. As such, this is only approximately correct. –  Jul 12 '11 at 21:42
  • Can you clarify how that works (and how false values are handled)? – jrdioko Jul 12 '11 at 21:49
  • `> @test` should be `> @test = nil` – EliadL Oct 22 '20 at 14:46
  • TIL: This does not work with hash values like `bodyParams['owner'] ||= ''` if there is no key called `'owner'`. It will give you an `IndexError`. For that, check here https://stackoverflow.com/a/39549523/1025430 – Chris Feb 14 '22 at 12:11
8

As you didn't specify what kind of variable:

v = v
v ||= 1

Don't recommend doing this with local variables though.

Edit: In fact v=v is not needed

Victor Moroz
  • 9,167
  • 1
  • 19
  • 23
  • 1
    It could be set to a falsey-value, however. As such, this is only approximately correct. –  Jul 12 '11 at 21:43
  • irb(main):007:0> v = v => nil // irb(main):008:0> v.class => NilClass // so same as initializing it as v = nil – Dmitri Jul 12 '11 at 21:48
  • 1
    @Dmitri No, it is not the same, if v has a value, it won't change. That is the point. – Victor Moroz Jul 13 '11 at 01:11
0

If the variable is not defined (declared?) it doesn't exist, and if it is declared then you know how you initialized it, right?

Usually, if I just need a variable whose use I don't yet know---that I know will never use as a Boolean---I initialize it by setting its value to nil. Then you can test if it has been changed later quite easily

x = nil


some code


if x do 
[code that will only run if x has changed]
end

that's all.

Dmitri
  • 2,658
  • 2
  • 25
  • 41
  • It could be set to a falsey-value, however. As such, this is only approximately correct. –  Jul 12 '11 at 21:43
  • that is quite true, @pst I don't use this technique if the expected value is a Boolean. – Dmitri Jul 12 '11 at 21:46