Class method vs constant in Ruby/Rails

前端 未结 4 803
野性不改
野性不改 2020-12-13 12:51

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, stil

相关标签:
4条回答
  • 2020-12-13 13:36

    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.

    0 讨论(0)
  • 2020-12-13 13:38

    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)

    0 讨论(0)
  • 2020-12-13 13:45

    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
    
    0 讨论(0)
  • 2020-12-13 13:52

    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.

    0 讨论(0)
提交回复
热议问题