在Ruby中实现枚举习语的最佳方法是什么?我正在寻找一些东西,我可以使用(几乎)像Java/ c#枚举。


当前回答

我很惊讶,没有人提供以下内容(从RAPI宝石中收获):

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

可以这样使用:

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

例子:

>> 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风格的常量/枚举时(就像使用FFI时一样,RAPI广泛使用FFI)。

此外,您不必像使用散列类型解决方案那样,担心拼写错误会导致无声失败。

其他回答

两种方式。符号(:foo符号)或常量(foo符号)。

当您希望增强可读性而不使用文字字符串乱写代码时,符号是合适的。

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

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

module Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end
 
flags = Foo::BAR | Foo::BAZ # flags = 3

增加了2021-01-17

如果您正在传递枚举值(例如,将其存储在数据库中),并且需要能够将值转换回符号,那么两种方法都可以混搭

COMMODITY_TYPE = {
  currency: 1,
  investment: 2,
}

def commodity_type_string(value)
  COMMODITY_TYPE.key(value)
end

COMMODITY_TYPE[:currency]

这个方法受到andrew-grimm的答案https://stackoverflow.com/a/5332950/13468的启发

我也建议阅读这里剩下的答案,因为有很多方法来解决这个问题,它实际上归结为你关心的其他语言的枚举

这是我在Ruby中处理枚举的方法。我想要的是简短而甜蜜的,不一定是最像c的。任何想法吗?

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
=> 5

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

Rails现在默认有枚举,不需要包含任何宝石。

这与Java、c++的枚举非常相似(并且具有更多的特性)。

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

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

我认为实现类似类型的枚举的最好方法是使用符号,因为它们的行为非常像整数(当涉及到性能时,object_id用于进行比较);你不需要担心索引,它们在你的代码xD中看起来非常整洁

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

尝试本机Set类型,例如:

>> 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"}>