39

I was implementing a form that includes a hard-coded dropdown for a collection and I was wondering what would be the best solution, I know both ways exposed below work, still I did as follows:

class Example

  # Options for Example.
  self.options
    [ 'Yes', 'No', 'Not sure' ]
  end
end

which is called by Example.options, but I know it is possible to do as follows as well:

class Example

  # Options for Example.
  OPTIONS = [ 'Yes', 'No', 'Not sure' ]
end

that would be called with Example::OPTIONS.

The question is, is any of these the good way or it just doesn't matter at all?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Heartcroft
  • 1,672
  • 2
  • 19
  • 31
  • The generally accepted approach is the latter as the Upercase with the comments is a form of documentation to the user that this is a constant – ilan berci Apr 09 '13 at 13:56
  • 3
    Unrelated to a possible answer, I would suggest using symbols over strings unless your code specifically needs strings. Hence `[:yes, :no, :not_sure]` – Charles Caldwell Apr 09 '13 at 14:01
  • 1
    @CharlesCaldwell or shorter `%i[yes no not_sure]`. – 3limin4t0r Jul 19 '18 at 18:15
  • In my opinion this issue is related to [Constants or class variables in ruby?](https://stackoverflow.com/q/387536/3982562) which does a better job explaining the mindset behind the options. Constants hold values that are constant and defined in the source code, variables holds an value that varies and a method should *do* something (other than returning a hard-coded array). – 3limin4t0r Jul 19 '18 at 18:41
  • We can use a method instead a Constant, if it is really NOT a constant to the class. Constants will be loaded at the time of loading the class. It is useful if you need this at this point. Else use a method, it get executed whenever we call it and we can also cache it. – Abhi Sep 10 '22 at 09:13

4 Answers4

39

The latter is better. If it were a method, a new array and new strings will be created every time it is called, which is a waste of resource.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • Fantastic, +10, if I could ;-) – Anand Shah Apr 09 '13 at 14:04
  • +1 Also calling `Example.methods` will tell options is a method, when you know is a class constant. It's another tip for making consistent code. – ichigolas Apr 09 '13 at 14:43
  • 3
    It is easy to cache the array in the method though... `@options ||= [...]` – Marc-André Lafortune Apr 09 '13 at 16:43
  • 1
    Wrote another answer, since I disagree that the constant is the better approach. – Marc-André Lafortune Apr 09 '13 at 16:49
  • The additional resources used by the class method will likely not be a problem for most applications. – Michael Stalker Jan 05 '15 at 19:26
  • 5
    Flexibility offered by methods is more important than penny-saving here (IMHO, obviously) – Sergio Tulentsev Dec 04 '15 at 09:21
  • @SergioTulentsev That is right in general. But I don't see anything better for using a method here. – sawa Dec 04 '15 at 10:04
  • mostly the constant will be evaluated in the load time, which is bad for example for rails, where in production it will be loaded up in the boot time. Specially if you have some static calculation, ie `MAX_TIME = 4*3600`, if you have many of such constants they can add up. Another thing is when you are testing, you loose the ability to stub it and test different behaviors, specially if you have the constant being composed by more constants or it can change from application to another or before boot time (i.e constant in a rails initializer) – VP. Jan 29 '20 at 11:32
37

TL;DR: It depends. Are the values meant to be used outside the class? Could they ever become dynamic? Could they change for subclasses?

As @sawa wrote, the downside of the method (written this way) is that a new array and strings are created each time.

A better way to write it would be:

class Example
  def self.options
    @options ||= ['Yes', 'No', 'Not sure']
  end
end

The array is stored in the instance variable @options, to avoid creating a new array each time.

Written this way, the method is very similar to the constant.

One key difference is if Example is subclassed, it will be more natural to refine the options method than the constant OPTIONS:

class Parent < Example
  def self.options
    @options ||= [*super, 'Extra']
  end
end

To do something similar with constants is difficult. Imagine that your list of options is used in a class method, this would look like:

class Example
  OPTIONS = ['Yes', 'No', 'Not sure']

  def self.foo(arg)
     puts "Available options:",
          self::OPTIONS  # The self:: is needed here
     # ...
  end
end

class Parent < Example
  OPTIONS = [*superclass::OPTIONS, 'Extra']
end

The tricky thing about constants, is that self::OPTIONS and OPTIONS are not the always same, while self.options and options are the same. Constants are usually used without specifying the scope (e.g. OPTIONS instead of self::OPTIONS) and inheritance will simply not work in that case.

Note that the method gives you the opportunity to make the result dynamic (i.e. return different results depending on other circumstances) without changing the API.

Final note: I'd recommend calling freeze on your array, to avoid anyone modifying it.

Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166
  • You can still do a similar thing with constants: `Example::Options = ["Yes", "No", "Not sure"]; Parent::Options = [*Example::Options, "Extra"]`. The only difference is that you have to explicitly write the parent class instead of using `super`. – sawa Apr 09 '13 at 17:16
  • 2
    @sawa: Indeed, you can (and you can use `superclass` instead of explicitly writing the parent class), but accessing the constants can be tricky. Edited my answer. – Marc-André Lafortune Apr 09 '13 at 17:27
  • The relevant idea in the question was to directly call the constant like `Example::OPTIONS` or `Parent::OPTIONS`, or `OPTIONS`. I don't get why you are defining `self.foo` to call them. – sawa Apr 09 '13 at 17:41
  • 1
    It's meant as an example of a class method using `options/OPTIONS`. – Marc-André Lafortune Apr 09 '13 at 17:46
9

What I usually do is have a mix of above-mentioned techniques:

class Player
  JURISDICTIONS = %i(de uk ru)

  def self.jurisdictions
    JURISDICTIONS
  end
end

It has a few advantages:

  • It provides a clean interface, encapsulating a constant (you call Player.jurisdictions instead of Player::JURISDICTIONS).
  • Additional logic can be added later just by altering method.
  • Method can be stubbed in tests.

IMHO, performance does not matter here.

Update: Constant can bee hidden using private_constant method (http://ruby-doc.org/core-2.3.0/Module.html#method-i-private_constant)

Artur INTECH
  • 6,024
  • 2
  • 37
  • 34
2

To further refine Artur's suggestion I would go with a class variable in order to hide visibility of the constant.

class Player
  @@jurisdictions = %i(de uk ru)

  def self.jurisdictions
    @@jurisdictions
  end
end
Kelly Stannard
  • 331
  • 2
  • 6
  • Works better, agree! But so far I was unable to solve inheritance problem described here: http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/ – Artur INTECH Dec 04 '15 at 09:03
  • After I wrote this I found out you should also consider class level instance variables: http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/ – Kelly Stannard Dec 23 '15 at 17:10