在Ruby中枚举
在Ruby中实现枚举习惯的最佳方式是什么? 我正在寻找一些我可以使用(几乎)像Java / C#枚举的东西。
两种方式。 符号( :foo
符号)或常量( FOO
符号)。
当您希望提高可读性时,符号是合适的,而不会乱丢文字string的代码。
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
最习惯的方式是使用符号。 例如,而不是:
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的精神。
符号也performance得很好。 例如,比较两个符号是否相等,比比较两个string要快得多。
我很惊讶,没有人提供类似于以下内容(从RAPIgem收获):
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广泛使用的情况下),这可以很好地发挥作用。
此外,您不必担心导致静默失败的拼写错误,就像使用散列types的解决scheme一样。
我使用的方法:
class MyClass MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2'] end
我喜欢它有以下优点:
- 它将视觉上的价值整体化
- 它做了一些编译时检查(与使用符号相反)
- 我可以轻松地访问所有可能的值的列表:只是
MY_ENUM
- 我可以轻松访问不同的值:
MY_VALUE_1
- 它可以具有任何types的值,而不仅仅是符号
符号可能会更好,因为如果您在另一个类( MyClass::MY_VALUE_1
)中使用它,则不必编写外部类的名称,
如果您使用的是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
这是我在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 => 3
查看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
我知道这家伙发布这个问题已经很长时间了,但是我也有同样的问题,这个post没有给我答案。 我想要一个简单的方法来查看数字代表什么,简单的比较,以及大部分ActiveRecord支持使用表示枚举的列进行查找。
我没有find任何东西,所以我做了一个叫做yinum的很好的实现,它允许我查找的所有东西。 制造了大量的规格,所以我很确定它是安全的。
一些示例function:
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
有人继续写了一个名叫Renum的rubygem。 它声称得到最接近Java / C#的行为。 就我个人而言,我还在学习Ruby,当我想让一个特定的类包含一个静态枚举(可能是一个哈希)时,我感到有点震惊,通过谷歌很难find它。
如果您担心使用符号进行拼写错误,请确保您的代码在使用不存在的键访问值时引发exception。 你可以通过使用fetch
而不是[]
:
my_value = my_hash.fetch(:key)
或者如果您提供不存在的密钥,则默认情况下会引发exception:
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
如果散列已经存在,则可以添加exception提升行为:
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
通常情况下,您不必担心常量的input错误。 如果拼错了名字,通常会引发exception。
最近我们发布了一个在Ruby中实现枚举的gem 。 在我的文章中,你会发现你的问题的答案。 另外我还描述了为什么我们的实现比现有的更好(实际上,在Ruby中这个特性有很多实现,但仍然是gem)。
这一切都取决于你如何使用Java或C#枚举。 你如何使用它将决定你将在Ruby中select的解决scheme。
尝试本地Set
types,例如:
>> 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"}>
符号是ruby的方式。 但是,有时候需要和一些C代码或者某些东西进行交stream,或者是为某些枚举公开某些枚举的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类
我已经实现了这样的枚举
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
也许最好的轻量级的方法是
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
这看起来有点多余,但是这是一个我已经使用了几次的方法,尤其是在我用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#枚举的严格性,它与模型绑定在一起。
大多数人使用符号(即:foo_bar
语法)。 他们是一种独特的不透明的价值观。 符号不属于任何枚举types,因此它们不是C的枚举types的忠实表示,但是它非常好。
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
有时我需要的是能够获取枚举值,并确定类似于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'
这对我来说是服务于枚举的目的,并保持它非常可扩展。 你可以添加更多的方法到枚举类和中提琴免费获得所有定义的枚举。 例如。 get_all_names和类似的东西。
另一种方法是使用包含名称和值的散列的Ruby类,如以下RubyFleebie博客文章中所述 。 这允许您在值和常量之间轻松地进行转换(特别是如果您添加一个类方法来查找给定值的名称)。
我认为像types一样实现枚举的最好方法是使用符号,因为几乎像整数一样(当涉及到执行时,使用object_id进行比较); 你不需要担心索引,他们看起来非常整洁你的代码xD
另一种模仿一致的平等处理的枚举(从戴夫·托马斯无耻地采用)。 允许打开枚举(很像符号)和closures(预定义)枚举。
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
另一个解决scheme是使用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'