loading...
Factorial

A trick with Ruby array literals

nflamel profile image Fran C. ・1 min read

Awesome Ruby tricks (3 Part Series)

1) A trick with Ruby array literals 2) A trick with Ruby Hash.new 3) A trick with Ruby anonymous classes

Declaring an array in Ruby is simple:

ary = ["foo", "bar"]
# => ["foo", "bar"]

but did you know you can save the array elements as variables the same time you declare them?

ary = [
  foo = "foo",
  bar = "bar"
]

foo # => "foo"
bar # => "bar"

Looks like magic? Well, it is not magic. This happens because assigning a value to a variable is an expression in Ruby. This is applicable to many other cases, like assigning a value on a conditional

if user = User.find(params[:id])
  puts "User is #{user.name}"
end 

Or even crazier: to save the parameter you pass to a method!

def some_method(arg)
  puts arg
end

some_method(value = "Hola")
# prints -> "Hola"

value
# => "Hola"

Disclaimer: ☝️ those 2 might be bad ideas. I wouldn't use it regularly on any code I write but knowing it is possible is always a good thing.

What can I use this for?

It plays quite well with constants that can then be used as some kind of enums:

COLORS = [
  COLOR_RED = "RED",
  COLOR_BLUE = "BLUE",
  COLOR_GREEN = "GREEN",
  COLOR_YELLOW = "YELLOW"
]
# => ["RED", "BLUE", "GREEN", "YELLOW"]

On rails apps they are especially useful to declare scopes:

scope :non_red, -> { where(color: COLORS - [COLOR_RED]) }
scope :blue, -> { where(color: COLOR_BLUE) }

Awesome Ruby tricks (3 Part Series)

1) A trick with Ruby array literals 2) A trick with Ruby Hash.new 3) A trick with Ruby anonymous classes

Posted on Nov 16 '17 by:

nflamel profile

Fran C.

@nflamel

I break code for a living... wait no, I fix it, most of the times. Well, sometimes.Yeah, I break code for a living.

Factorial

Everything you need to manage your HR processes Spend less time doing administrative HR tasks and focus on what matters.

Discussion

markdown guide
 

Good information and well-written! At one of my companies, I picked up the pattern of making a class for constants within their namespace, and referring to the array of them as ALL. Example:

class BikeShed
  module Colors
    ALL = [
      RED = 'red',
      BLUE = 'blue',
      # ...
    ]
  end

  # ...
end

This was especially helpful as more and more categories of constants became part of the codebase

 

Never thought about using a separate module to keep them grouped but it is a really good technique.

I topically use this in a way in which the constants end up being almost private.
I either wrap the value checks in small predicate methods:

def red?
  color == COLORS_RED
end

Or if I need them with active record I create scopes like the ones in the post.

Even in tests I try to avoid using them for setup and if I have factory_bot at hand I create traits for the different values instead.

This way you end up with code which is a bit more decoupled from this specific implementation detail and when your constant is not enough and you need to build an object instead you'll save a lot of grepping and sedding😅

 

There's some good enum style tricks on this page! Thanks to both of you for sharing.

 

Thanks to you for reading!

 

Interesting trick to create enums values on the fly, though I guess those are variables not constants, but maybe one can sneak a .freeze inline 😂

 

I guess those are variables not constants

This sentence confused me. Ruby uses the case of the LHS to determine if something is considered a constant or not (they can still be are-assigned though, but at least you'll get a warning). .freeze only freezes the RHS, so it can't be further mutated, but that doesn't really change Ruby's constant semantics.

RED = 'red'
Object.const_defined?('RED')
#=> true
local_variables
#=> [:_]
RED = 'blue'
(irb):4: warning: already initialized constant RED
(irb):1: warning: previous definition of RED was here
RED
#=> "blue"
RED << ' oh no'
#=> "blue oh no"
RED = 'red'.freeze
(irb):7: warning: already initialized constant RED
(irb):4: warning: previous definition of RED was here
RED << ' oh no'
Traceback (most recent call last):
...
FrozenError (can't modify frozen String: "red")
RED = 'green'
(irb):9: warning: already initialized constant RED
(irb):7: warning: previous definition of RED was here
RED
#=> "green"

Did I misunderstand what you meant? Or was your comment simply referring to the fact that Ruby constants aren't quite as constant when they are assigned a mutable value?

 

Sure! Nothing is really constant in Ruby... you can even use symbols if they fit your need.

 

I do this...

if user = User.find(params[:id])
puts "User is #{user.name}"
end

All the time!

Does this make me a bad person?

 

xD not at all.

The reason why I prefer to keep declaration and conditionals separated is because it helps a bit with reading the code and I've found more than once that it is not always understood!

I've seen teammates trying to "fix" it as if user == User.find(params[:id]) because they weren't understanding I was just assigning something! Besides, Ruby emits a warning in this case... so it is not clear at all even when you can do it.

The difference with the case of the constants is that the constants declaration doesn't usually involve your applications's logic, so it is easier to reason about and less "dangerous".