348

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

4

25 回答 25

347

两种方式。符号(: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

我还建议通读此处的其余答案,因为有很多方法可以解决此问题,并且归结为您关心的其他语言枚举的含义

于 2008-09-16T19:32:30.110 回答
60

我很惊讶没有人提供类似以下的东西(从RAPI gem 中获取):

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 风格的常量/枚举时(如使用RAPI 广泛使用的FFI时就是这种情况)效果很好。

此外,您不必担心拼写错误会导致静默失败,就像使用哈希类型解决方案一样。

于 2011-05-29T21:19:58.597 回答
52

最惯用的方法是使用符号。例如,而不是:

enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

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

# 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 精神。

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

于 2008-09-16T19:06:04.790 回答
47

我使用以下方法:

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

我喜欢它有以下优点:

  1. 它在视觉上将值组合为一个整体
  2. 它会进行一些编译时检查(与仅使用符号相反)
  3. 我可以轻松访问所有可能值的列表:只需MY_ENUM
  4. 我可以轻松访问不同的值:MY_VALUE_1
  5. 它可以有任何类型的值,而不仅仅是 Symbol

符号可能会更好,因为您不必写外部类的名称,如果您在另一个类中使用它(MyClass::MY_VALUE_1

于 2011-04-15T10:46:46.657 回答
17

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

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

这与 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
于 2014-12-08T00:04:22.303 回答
11

我知道这家伙发布这个问题已经很久了,但我有同样的问题,而这篇文章没有给我答案。我想要一种简单的方法来查看数字代表什么,轻松比较,最重要的是 ActiveRecord 支持使用表示枚举的列进行查找。

我什么也没找到,所以我做了一个很棒的实现,叫做yinum,它允许我寻找任何东西。做了很多规格,所以我很确定它是安全的。

一些示例功能:

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
于 2012-12-07T13:46:41.670 回答
10

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

class Gender
  include Enum

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

Gender.all
Gender::MALE
于 2011-03-16T21:46:48.707 回答
10

这是我在 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
于 2012-07-12T15:48:35.760 回答
8

也许最好的轻量级方法是

module MyConstants
  ABC = Class.new
  DEF = Class.new
  GHI = Class.new
end

这种方式值具有关联的名称,如在 Java/C# 中:

MyConstants::ABC
=> MyConstants::ABC

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

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

如果你想要一个枚举的序数值,你可以做

MyConstants.constants.index :GHI
=> 2
于 2014-12-19T21:50:52.597 回答
7

如果您担心符号的拼写错误,请确保您的代码在您使用不存在的键访问值时引发异常。您可以通过使用fetch而不是[]

my_value = my_hash.fetch(:key)

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

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

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

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

通常,您不必担心常量的错字安全。如果你拼错了一个常量名,它通常会引发一个异常。

于 2011-03-16T23:14:48.757 回答
4

这完全取决于您如何使用 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"}>
于 2008-09-16T20:35:24.310 回答
4

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

于 2009-03-04T20:47:49.597 回答
4

最近我们发布了一个在 Ruby中实现Enums的gem。在我的帖子中,您将找到问题的答案。我还在那里描述了为什么我们的实现比现有的更好(实际上在 Ruby 中这个特性有很多实现作为 gems)。

于 2015-12-28T17:53:47.860 回答
4

另一种解决方案是使用 OpenStruct。它非常直接和干净。

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

例子:

# 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'
于 2017-07-14T07:58:12.393 回答
2

符号是红宝石的方式。但是,有时需要与一些 C 代码或其他东西或 Java 交谈,这些代码为各种事物公开了一些枚举。


#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

然后可以这样使用


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

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

于 2008-10-02T20:47:41.460 回答
2

我已经实现了这样的枚举

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

然后它很容易做操作

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values
于 2012-02-28T13:38:05.410 回答
2
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)

输出:

GOOD
于 2012-07-11T12:29:01.167 回答
2

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

#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

这给了我 ac# 枚举的严谨性,并且它与模型相关联。

于 2013-04-16T20:09:44.973 回答
1

大多数人使用符号(这是:foo_bar语法)。它们是一种独特的不透明值。符号不属于任何枚举风格的类型,因此它们并不是 C 枚举类型的真正忠实表示,但这几乎与它得到的一样好。

于 2008-09-16T19:04:45.887 回答
1
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 - a
2 - b
3 - c
4 - d

于 2012-03-06T11:38:10.390 回答
1

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

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 类添加更多方法,viola 在所有已定义的枚举中免费获取它们。例如。get_all_names 之类的东西。

于 2015-08-21T21:54:19.370 回答
1

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

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

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

于 2018-01-11T04:47:02.487 回答
0

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

于 2009-09-29T18:07:32.430 回答
0

我认为实现类型枚举的最佳方法是使用符号,因为它几乎表现为整数(在性能方面,object_id 用于进行比较);您无需担心索引,它们在您的代码中看起来非常整洁 xD

于 2010-04-03T23:57:49.383 回答
0

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

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
于 2012-12-30T01:44:43.050 回答