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?
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.
#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"
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
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.
Related
From what I understand, a ||= 7 means the following:
if a has a value, continue using that value, but if it does NOT have one, then set it to 7.
Here is what happens though.
If i have a and b as:
a = true
b = false
then
a ||= b => true
(in my interpretation: since 'a' DOES have a value, it remains that, and does not get equated to 'false' - so far so good.)
However, if i have them switched up like:
a = false
b = true
then a ||= b => true
so in this case my logic does not work, since it should return false, as "since 'a' has a value, it should not be assigned the value of 'b'", which apparently happens here.
Am I missing something?
a ||= b
is equivalent to
a || a = b
this means b value is assigned to a if a is falsy, i.e. false or nil.
I have #obj.items_per_page, which is 20 at the beginning, and I want the method below to assign value to it only if many_items is not nil:
def fetch_it_baby (many_items = nil)
#obj.items_per_page = many_items
With the code above, even if many_items is nil, #obj.items_per_page remains at 20. Why? And is that "good" coding? Shouldn't I use something like
#obj.items_per_page = many_items || #obj.items_per_page
Or is there a third way? I don't feel completely comfortable with either way.
The style I generally see looks like this:
#obj.items_per_page = many_items if many_items
This uses the inline conditional, while avoiding negative or double-negative conditions.
I suggest the following as it makes it clear that you have a default value for the assignment in case the caller did not specify many_items in the call:
def function(argument = nil)
variable = argument || 20
...
end
However, since you specified that the assignment takes places only if the value is not nil then you'll need to check for the nil value otherwise you will miss the assignment if the value was false. If you really need that case then the solution is more long-winded:
def function(argument = nil)
variable = argument.nil? ? 20 : argument
...
end
You can use &&= (in the same way as ||= is used to assign only if nil or false)
> a = 20 # => 20
> a &&= 30 # => 30
> a # => 30
> a = nil # => nil
> a &&= 30 # => nil
> a = false # => false
> a &&= 30 # => false
> a = {} # => {}
> a &&= 30 # => 30
remember though
> a = 30 # => 30
> a &&= nil # => nil
> a &&= false # => nil
> b &&= 3 # => nil
Even if many_items is nil #obj.items_per_page remains at 20
That sounds like whatever class #obj is has a custom modifier method items_per_page= that only updates the value if the new value is not nil. This is not standard Ruby. For example, given this definition:
class Demo
attr_accessor :items_per_page
end
I get this behavior:
irb(main):005:0> demo = Demo.new #=> #<Demo:0x007fb7b2060240>
irb(main):006:0> demo.items_per_page = 20 #=> 20
irb(main):007:0> demo.items_per_page #=> 20
irb(main):008:0> demo.items_per_page = nil #=> nil
irb(main):009:0> demo.items_per_page #=> nil
As for your example, I would probably write it this way:
#obj.items_per_page = many_items unless many_items.nil?
For Rails you can also use presence as described here
region = params[:state].presence || params[:country].presence || 'US'
new-alexandria's answer is my go-to, but another "third way" would be to use a ternary:
class Demo
attr_accessor :items_per_page
end
many_items = 100
#obj = Demo.new
#obj.items_per_page = 20 #=> 20
#obj.items_per_page = !many_items.nil? ? 30 : nil #=> 30
I am using Rails and I have a similar need.
You can define a method on your model:
class Gift < ApplicationRecord
def safe_set(attribute, value)
return if value.nil?
send("#{attribute}=", value)
end
end
So you can do
g = Gift.new
g.colour = 'red'
g.safe_set(:colour, nil)
g.colour -> 'red'
g.safe_set(:colour, 'green')
g.colour -> 'green'
We have one more method in rails that can help to remove values that are nil.
compact
This method can be used with an array, hash. Returns the same data removing all values that are nil
Eg :
array.compact
Further reference:
https://apidock.com/ruby/v1_9_3_392/Array/compact
If many items is a variable the if/unless versions above are the best. But if many_items is actually a method you don't want to evaluate multiple times I find the following useful.
#obj.items_per_page = many_items.presence || #obj.items_per_page
This isn't quite what you want since it won't assign empty strings or other non-nil but non-present values but most of the time I want to do this kind of thing this works for me (it does create a self-assignment when the value is nil/non-present so only use it if your setters are idempotent).
I have seen and used by myself lots of ||= in Ruby code, but I have almost never seen or used &&= in practical application. Is there a use case for &&=?
Not to be glib, but the obvious use case is any time you would say x && foo and desire the result stored back into x. Here's one:
list = [[:foo,1],[:bar,2]]
result = list.find{ |e| e.first == term }
result &&= result.last # nil or the value part of the found tuple
any sort of iteration where you want to ensure that the evaluation of a boolean condition on all elements returns true for all the elements.
e.g.
result = true
array.each do |elem|
# ...
result &&= condition(elem) # boolean condition based on element value
end
# result is true only if all elements return true for the given condition
Validation of the document such as,
a = {'a' => ['string',123]}
where the element in a['a'] should be a String. To validate them, I think you can use,
def validate(doc, type)
valid = true
doc.each{|x|
valid &&= x.is_a? type
}
valid
end
1.9.3p392 :010 > validate(a['a'],String) => false
I was under the impression that conditions joined with && were executed in sequence, such that the following would return true:
a = "adasd"
> b = a && b.present?
=> false
Thoughts?
Thanks!
--Peter
note:
b = a
=> "adasd"
b.present?
=> true
When you say this:
b = a && b.present?
You're declaring b as a local variable but it will be nil until the right side of the assignment is evaluated. In particular, b will be nil when you call present? on it and the conjunction will be false making b false.
When you do this:
a = 'pancakes'
b = a
b.present?
b will have the value 'pancakes' when you call present? on it so you get a true return from b.present?.
As per the rails doc
present is checking for a variable to be non blank
As per assignment timing, ruby declares the variable in the scope as soon as it sees it, so b will be in the scope but with no value, so present will return false.
You should maybe compared this with defined?
a = "abc"
=> "abc"
defined? a
=> "local-variable"
defined? b
=> nil
b = defined? b
=> "local-variable"
In SO question 2068165 one answer raised the idea of using something like this:
params[:task][:completed_at] &&= Time.parse(params[:task][:completed_at])
as a DRYer way of saying
params[:task][:completed_at] = Time.parse(params[:task][:completed_at]) if params[:task][:completed_at]
where the params Hash would be coming from a (Rails/ActionView) form.
It's a kind of corollary to the well-known ||= idiom, setting the value if the LHS is not nil/false.
Is using &&= like this actually a recognised Ruby idiom that I've somehow missed or have I just forgotten a more commonly-used idiom? It is getting rather late...
It ought to be. If nothing else, params[:task] is only evaluated once when using the &&= form.
To clarify:
params[:task][:completed_at] = params[:task][:completed_at] && ...
calls [](:task) on params twice, [](:completed_at) and []=(:completed_at) once each on params[:task].
params[:task][:completed_at] &&= ...
calls [](:task) on params once, and its value is stashed away for both the [](:completed_at) and []=(:completed_at) calls.
Actual example describing what I'm trying to illustrate (based on Marc-Andre's example code; much thanks):
class X
def get
puts "get"
#hash ||= {}
end
end
irb(main):008:0> x = X.new
=> #<X:0x7f43c496b130>
irb(main):009:0> x.get
get
=> {}
irb(main):010:0> x.get[:foo] = 'foo'
get
=> "foo"
irb(main):011:0> x.get[:foo]
get
=> "foo"
irb(main):012:0> x.get[:foo] &&= 'bar'
get
=> "bar"
irb(main):013:0> x.get[:foo] = x.get[:foo] && 'bar'
get
get
=> "bar"
Note that using the "expanded" form causes "get" to be printed out twice, but using the compact form causes it to only be printed once.
Using &&=, in the case of LHS is false, it is only being read once, but not being set. This should make it clearer ...
class Test
def initialize(value)
#v = value
end
def v=(value)
puts "set"
#v = value
end
def v
puts "get=>#{#v}"
#v
end
end
t = Test.new(true)
t.v = t.v && true
puts '----'
t.v &&= true
puts '----'
t = Test.new(false) # lets make LHS false
t.v = t.v && true
puts '----'
t = Test.new(false) # lets make LHS false
t.v &&= true
The result:
get=>true
set
----
get=>true
set
----
get=>false
set
----
get=>false