简体   繁体   中英

Class method vs constant in Ruby/Rails

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 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.

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 (eg 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 (ie 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.

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 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.
  • The 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 )

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM