如何在Ruby中实现枚举?

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

226040 次浏览

大多数人使用符号(这是:foo_bar语法)。它们是一种独特的不透明值。符号不属于任何枚举类型,所以它们不是C的枚举类型的忠实表示,但这几乎是最好的。

最常用的方法是使用符号。例如,不要:

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

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

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

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

这完全取决于你如何使用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"}>

符号是红宝石之道。然而,有时我们需要与一些C代码或Java进行对话,这些代码或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类

有人更进一步,写了一个名为Renum的红宝石宝石。它声称获得了最接近Java/ c#的行为。就我个人而言,我仍然在学习Ruby,当我想让一个特定的类包含一个静态枚举(可能是一个散列)时,我有点震惊,因为它不太容易通过谷歌找到。

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

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

查看ruby-enum宝石https://github.com/dblock/ruby-enum

class Gender
include Enum


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


Gender.all
Gender::MALE

如果您担心符号的拼写错误,请确保您的代码在访问具有不存在键的值时引发异常。你可以通过使用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

通常情况下,您不必担心常量的拼写安全问题。如果你拼错了常量名,通常会引发异常。

我使用以下方法:

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)

我很惊讶,没有人提供像下面这样的东西(从加固宝石中收获):

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广泛使用)。

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

我实现过这样的枚举

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

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

这是我在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

我知道自从那个人发布这个问题已经有很长时间了,但我也有同样的问题,这篇文章没有给我答案。我想要一种简单的方法来查看数字代表什么,便于比较,最重要的是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

另一种使用一致的平等处理来模拟枚举的方法(无耻地采用了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

这似乎有点多余,但这是我使用过几次的方法,特别是在集成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

这给了我c#枚举的严谨性,并且它与模型绑定。

如果你使用的是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

也许最好的轻量级方法是

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

有时我所需要的是能够获取enum的值,并识别其名称类似于java世界。

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可以在所有已定义的Enum中免费获得它们。为例。Get_all_names之类的。

最近,我们发布了实现Ruby中的枚举宝石。在我的帖子中,你会找到你问题的答案。我还描述了为什么我们的实现比现有的更好(实际上在Ruby中有很多这个特性的实现)。

另一种解决方案是使用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'

试试看。 https://github.com/alfa-jpn/inum < / p >

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