postal_code"/>

如何在Ruby中实现枚举?

如何在Ruby中实现枚举?

How to implement Enums in Ruby?

在Ruby中实现枚举用法的最佳方法是什么? 我正在寻找可以(几乎)使用的东西,例如Java / C#枚举。


两种方式。符号(:foo表示法)或常量(FOO表示法)。

当您想提高可读性而不用文字字符串乱码时,使用符号是合适的。

1
2
postal_code[:minnesota] ="MN"
postal_code[:new_york] ="NY"

当您具有重要的基础值时,常数是适当的。只需声明一个模块来保存您的常量,然后在其中声明常量。

1
2
3
4
5
6
7
module Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end

flags = Foo::BAR | Foo::BAZ # flags = 3

我很惊讶没有人提供以下内容(从RAPI gem中获得):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Enum

  private

  def self.enum_attr(name, num)
    name = name.to_s

    define_method(name + '?') do
      @attrs & num != 0
    end

    define_method(name + '=') do |set|
      if set
        @attrs |= num
      else
        @attrs &= ~num
      end
    end
  end

  public

  def initialize(attrs = 0)
    @attrs = attrs
  end

  def to_i
    @attrs
  end
end

可以这样使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class FileAttributes < Enum
  enum_attr :readonly,       0x0001
  enum_attr :hidden,         0x0002
  enum_attr :system,         0x0004
  enum_attr :directory,      0x0010
  enum_attr :archive,        0x0020
  enum_attr :in_rom,         0x0040
  enum_attr :normal,         0x0080
  enum_attr :temporary,      0x0100
  enum_attr :sparse,         0x0200
  enum_attr :reparse_point,  0x0400
  enum_attr :compressed,     0x0800
  enum_attr :rom_module,     0x2000
end

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>> example = FileAttributes.new(3)
=> #<FileAttributes:0x629d90 @attrs=3>
>> example.readonly?
=> true
>> example.hidden?
=> true
>> example.system?
=> false
>> example.system = true
=> true
>> example.system?
=> true
>> example.to_i
=> 7

这在数据库方案中或在处理C样式常量/枚举(如使用RAPI广泛使用的FFI的情况)时非常有效。

而且,您不必担心像输入错误一样会导致无提示失败,就像使用哈希类型的解决方案一样。


最惯用的方法是使用符号。例如,代替:

1
2
3
4
5
6
7
enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

...您可以只使用符号:

1
2
3
4
5
6
7
# You don't actually need to declare these, of course--this is
# just to show you what symbols look like.
:foo
:bar
:baz

my_func(:foo)

它比枚举更开放,但与Ruby精神非常契合。

符号的表现也很好。例如,比较两个符号是否相等,比比较两个字符串要快得多。


我使用以下方法:

1
2
3
class MyClass
  MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end

我喜欢它具有以下优点:

  • 视觉上将价值分组为一个整体
  • 它会进行一些编译时检查(与仅使用符号相反)
  • 我可以轻松访问所有可能值的列表:仅MY_ENUM
  • 我可以轻松访问不同的值:MY_VALUE_1
  • 它可以具有任何类型的值,而不仅仅是Symbol
  • 符号可能更好,因为如果您在另一个类中使用它,则不必写外部类的名称(MyClass::MY_VALUE_1)


    如果您使用的是Rails 4.2或更高版本,则可以使用Rails枚举。

    Rails现在默认情况下具有枚举,无需包含任何gem。

    这与Java,C ++枚举非常相似(并且在功能上更多)。

    引用自http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Conversation < ActiveRecord::Base
      enum status: [ :active, :archived ]
    end

    # conversation.update! status: 0
    conversation.active!
    conversation.active? # => true
    conversation.status  # =>"active"

    # conversation.update! status: 1
    conversation.archived!
    conversation.archived? # => true
    conversation.status    # =>"archived"

    # conversation.update! status: 1
    conversation.status ="archived"

    # conversation.update! status: nil
    conversation.status = nil
    conversation.status.nil? # => true
    conversation.status      # => nil

    我知道自从这个人发布这个问题已经很久了,但是我有同样的问题,而这个发布并没有给我答案。我想要一种简单的方法来查看数字表示的内容,进行简单的比较以及使用表示枚举的列进行查找的大多数ActiveRecord支持。

    我什么都没找到,所以我做了一个叫yinum的出色实现,它允许我寻找的所有内容。做了很多规格,所以我很确定它是安全的。

    一些示例功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    COLORS = Enum.new(:COLORS, :red => 1, :green => 2, :blue => 3)
    => COLORS(:red => 1, :green => 2, :blue => 3)
    COLORS.red == 1 && COLORS.red == :red
    => true

    class Car < ActiveRecord::Base    
      attr_enum :color, :COLORS, :red => 1, :black => 2
    end
    car = Car.new
    car.color = :red /"red" / 1 /"1"
    car.color
    => Car::COLORS.red
    car.color.black?
    => false
    Car.red.to_sql
    =>"SELECT `cars`.* FROM `cars` WHERE `cars`.`color` = 1"
    Car.last.red?
    => true

    这是我在Ruby中枚举的方法。我追求短暂而甜蜜,不一定是最喜欢C的。有什么想法吗?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    module Kernel
      def enum(values)
        Module.new do |mod|
          values.each_with_index{ |v,i| mod.const_set(v.to_s.capitalize, 2**i) }

          def mod.inspect
           "#{self.name} {#{self.constants.join(', ')}}"
          end
        end
      end
    end

    States = enum %w(Draft Published Trashed)
    => States {Draft, Published, Trashed}

    States::Draft
    => 1

    States::Published
    => 2

    States::Trashed
    => 4

    States::Draft | States::Trashed
    => 3

    查看ruby-enum gem,https://github.com/dblock/ruby-enum。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Gender
      include Enum

      Gender.define :MALE,"male"
      Gender.define :FEMALE,"female"
    end

    Gender.all
    Gender::MALE

    也许最好的轻量级方法是

    1
    2
    3
    4
    5
    module MyConstants
      ABC = Class.new
      DEF = Class.new
      GHI = Class.new
    end

    这样,值具有关联的名称,如Java / C#中那样:

    1
    2
    MyConstants::ABC
    => MyConstants::ABC

    要获取所有值,您可以执行

    1
    2
    MyConstants.constants
    => [:ABC, :DEF, :GHI]

    如果您想要枚举的序数值,则可以执行

    1
    2
    MyConstants.constants.index :GHI
    => 2


    如果您担心带有符号的错字,请确保当您使用不存在的键访问值时,代码会引发异常。您可以使用fetch而不是[]来做到这一点:

    1
    my_value = my_hash.fetch(:key)

    或者如果您提供不存在的密钥,则默认情况下通过使哈希引发异常来实现:

    1
    2
    3
    my_hash = Hash.new do |hash, key|
      raise"You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"
    end

    如果哈希已经存在,则可以添加异常引发行为:

    1
    2
    3
    4
    my_hash = Hash[[[1,2]]]
    my_hash.default_proc = proc do |hash, key|
      raise"You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"
    end

    通常,您不必担心常量的错字安全性。如果您拼写错误的常量名称,通常会引发异常。


    有人继续写了一个名为Renum的红宝石。它声称获得最接近的Java / C#行为。就我个人而言,我仍在学习Ruby,当我想让特定的类包含静态枚举(可能是哈希)时,我感到有些震惊,因为它不是通过Google完全找到的。


    另一个解决方案是使用OpenStruct。它非常简单和干净。

    https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    # bar.rb
    require 'ostruct' # not needed when using Rails

    # by patching Array you have a simple way of creating a ENUM-style
    class Array
       def to_enum(base=0)
          OpenStruct.new(map.with_index(base).to_h)
       end
    end

    class Bar

        MY_ENUM = OpenStruct.new(ONE: 1, TWO: 2, THREE: 3)
        MY_ENUM2 = %w[ONE TWO THREE].to_enum

        def use_enum (value)
            case value
            when MY_ENUM.ONE
                puts"Hello, this is ENUM 1"
            when MY_ENUM.TWO
                puts"Hello, this is ENUM 2"
            when MY_ENUM.THREE
                puts"Hello, this is ENUM 3"
            else
                puts"#{value} not found in ENUM"
            end
        end

    end

    # usage
    foo = Bar.new    
    foo.use_enum 1
    foo.use_enum 2
    foo.use_enum 9


    # put this code in a file 'bar.rb', start IRB and type: load 'bar.rb'

    最近,我们发布了一个在Ruby中实现Enums的gem。在我的帖子中,您将找到有关问题的答案。我还在那里描述了为什么我们的实现比现有的更好(实际上,Ruby中有很多此功能的实现,但它们都是宝石)。


    这完全取决于您如何使用Java或C#枚举。如何使用它将决定您将在Ruby中选择的解决方案。

    尝试使用本机Set类型,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >> enum = Set['a', 'b', 'c']
    => #<Set: {"a","b","c"}>
    >> enum.member?"b"
    => true
    >> enum.member?"d"
    => false
    >> enum.add?"b"
    => nil
    >> enum.add?"d"
    => #<Set: {"a","b","c","d"}>


    符号是红宝石的方式。但是,有时需要与一些C代码或某些Java或某些暴露各种事物的Java进行交谈。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #server_roles.rb
    module EnumLike

      def EnumLike.server_role
        server_Symb=[ :SERVER_CLOUD, :SERVER_DESKTOP, :SERVER_WORKSTATION]
        server_Enum=Hash.new
        i=0
        server_Symb.each{ |e| server_Enum[e]=i; i +=1}
        return server_Symb,server_Enum
      end

    end

    然后可以这样使用

    1
    2
    3
    4
    5
    require 'server_roles'

    sSymb, sEnum =EnumLike.server_role()

    foreignvec[sEnum[:SERVER_WORKSTATION]]=8

    当然可以将其抽象化,您可以滚动我们自己的Enum类


    我已经实现了这样的枚举

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    module EnumType

      def self.find_by_id id
        if id.instance_of? String
          id = id.to_i
        end
        values.each do |type|
          if id == type.id
            return type
          end
        end
        nil
      end

      def self.values
        [@ENUM_1, @ENUM_2]
      end

      class Enum
        attr_reader :id, :label

        def initialize id, label
          @id = id
          @label = label
        end
      end

      @ENUM_1 = Enum.new(1,"first")
      @ENUM_2 = Enum.new(2,"second")

    end

    那么它很容易操作

    1
    EnumType.ENUM_1.label

    ...

    1
    enum = EnumType.find_by_id 1

    ...

    1
    valueArray = EnumType.values

    这似乎有点多余,但这是我使用过几次的方法,尤其是在我与xml或类似的东西集成的地方。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #model
    class Profession
      def self.pro_enum
        {:BAKER => 0,
         :MANAGER => 1,
         :FIREMAN => 2,
         :DEV => 3,
         :VAL => ["BAKER","MANAGER","FIREMAN","DEV"]
        }
      end
    end

    Profession.pro_enum[:DEV]      #=>3
    Profession.pro_enum[:VAL][1]   #=>MANAGER

    这给了我一个C#枚举的严谨性,并且与模型紧密相关。


    大多数人使用符号(这是:foo_bar语法)。它们是唯一的不透明值。符号不属于任何枚举样式类型,因此它们实际上并不是C枚举类型的忠实表示,但这几乎是一样好。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    module Status
      BAD  = 13
      GOOD = 24

      def self.to_str(status)
        for sym in self.constants
          if self.const_get(sym) == status
            return sym.to_s
          end
        end
      end

    end


    mystatus = Status::GOOD

    puts Status::to_str(mystatus)

    输出:

    1
    GOOD


    有时候,我所需要的只是能够获取枚举的值并识别其名称,类似于java world。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    module Enum
         def get_value(str)
           const_get(str)
         end
         def get_name(sym)
           sym.to_s.upcase
         end
     end

     class Fruits
       include Enum
       APPLE ="Delicious"
       MANGO ="Sweet"
     end

     Fruits.get_value('APPLE') #'Delicious'
     Fruits.get_value('MANGO') # 'Sweet'

     Fruits.get_name(:apple) # 'APPLE'
     Fruits.get_name(:mango) # 'MANGO'

    对我来说,这符合枚举的目的,并且使其保持可扩展性。您可以向Enum类添加更多方法,中提琴可以在所有定义的枚举中免费获取它们。例如。 get_all_names之类的东西。


    1
    2
    3
    4
    5
    6
    7
    irb(main):016:0> num=[1,2,3,4]
    irb(main):017:0> alph=['a','b','c','d']
    irb(main):018:0> l_enum=alph.to_enum
    irb(main):019:0> s_enum=num.to_enum
    irb(main):020:0> loop do
    irb(main):021:1* puts"#{s_enum.next} - #{l_enum.next}"
    irb(main):022:1> end

    输出:

    1-一个
    2-b
    3-c
    4天


    我认为实现像类型这样的枚举的最佳方法是使用符号,因为符号的行为几乎都是整数(当涉及到性能时,使用object_id进行比较);您无需担心索引编制,它们在您的代码xD中看起来非常整洁


    另一种方法是将Ruby类与包含名称和值的哈希一起使用,如以下RubyFleebie博客文章中所述。这使您可以轻松地在值和常量之间转换(尤其是如果您添加类方法以查找给定值的名称时)。


    模仿具有一致的相等性处理的枚举的另一种方法(从Dave Thomas中无耻地采用)。允许打开的枚举(很像符号)和关闭的(预定义)枚举。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Enum
      def self.new(values = nil)
        enum = Class.new do
          unless values
            def self.const_missing(name)
              const_set(name, new(name))
            end
          end

          def initialize(name)
            @enum_name = name
          end

          def to_s
           "#{self.class}::#@enum_name"
          end
        end

        if values
          enum.instance_eval do
            values.each { |e| const_set(e, enum.new(e)) }
          end
        end

        enum
      end
    end

    Genre = Enum.new %w(Gothic Metal) # creates closed enum
    Architecture = Enum.new           # creates open enum

    Genre::Gothic == Genre::Gothic        # => true
    Genre::Gothic != Architecture::Gothic # => true


    尝试inum。
    https://github.com/alfa-jpn/inum

    1
    2
    3
    4
    5
    class Color < Inum::Base
      define :RED
      define :GREEN
      define :BLUE
    end
    1
    2
    3
    Color::RED
    Color.parse('blue') # => Color::BLUE
    Color.parse(2)      # => Color::GREEN

    查看更多https://github.com/alfa-jpn/inum#usage


    快速又肮脏,感觉就像C#:

    1
    2
    3
    4
    5
    class FeelsLikeAnEnum
      def self.Option_1() :option_1 end
      def self.Option_2() :option_2 end
      def self.Option_3() :option_3 end
    end

    像使用枚举一样使用它:

    1
    method_that_needs_options(FeelsLikeAnEnum.Option_1)

    推荐阅读