当前位置:  开发笔记 > 编程语言 > 正文

Ruby中的枚举

如何解决《Ruby中的枚举》经验,为你挑选了10个好方法。

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



1> mlibby..:

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


一个模块不会更好地组合常量 - 因为你不会做任何实例吗?
只是评论.Ruby在命名约定方面有点痛苦但在你绊倒它们之前并不是很明显.枚举的名称必须全部为大写,并且模块名称的第一个字母必须大写为ruby才能知道模块是常量模块.
不完全正确.常量的第一个字母必须大写,但不是所有字母都必须.这是一个公约偏好的问题.例如,所有模块名称和类名实际上也是常量.
如果将这些枚举存储到数据库中该怎么办?符号表示法有效吗?我怀疑...
我非常喜欢第二个想法.谢谢!

2> Charles..:

我很惊讶没有人提供类似下面的东西(从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)
=> #
>> example.readonly?
=> true
>> example.hidden?
=> true
>> example.system?
=> false
>> example.system = true
=> true
>> example.system?
=> true
>> example.to_i
=> 7

这在数据库场景中或在处理C样式常量/枚举时都很好(当使用FFI时,RAPI大量使用它).

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



3> emk..:

最常用的方法是使用符号.例如,而不是:

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精神很吻合.

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


所以Ruby精神是:"错别字会编译"
流行的Ruby框架在很大程度上依赖于运行时元编程,并且执行过多的加载时间检查会剥夺Ruby的大部分表达能力.为了避免出现问题,大多数Ruby程序员都采用测试驱动设计,不仅会发生拼写错误,还会发现逻辑错误.
@yar:嗯,语言设计是一系列的权衡,语言特征是相互作用的.如果你想要一个好的,高度动态的语言,那么请使用Ruby,首先编写单元测试,并遵循语言的精神.:-)如果这不是你想要的,那里有许多其他优秀的语言,每个语言都有不同的权衡.
@emk,我同意,但我个人的问题是我觉得Ruby很舒服,但我觉得在Ruby中重构不太舒服.现在我已经开始编写单元测试(最后),我意识到它们不是灵丹妙药:我的猜测是1)Ruby代码没有经常大量重构,在实践中2)Ruby不是结束就动态语言而言,正是因为它很难自动重构.看看我的问题2317579,奇怪的是,Smalltalk人员接管了这个问题.
是的,但是使用这些字符串不符合C#语言的精神,这只是一种不好的做法.
@emk,谢谢你:所以基本上你用编译时人工编写的测试代码替换编译时类型检查.除了后者更全面的事实,它听起来不是一个非常好的权衡(更多的工作).但是经过几个月的测试后,我会让你知道我的想法.
@Max,Ruby工作的方式,在编译时似乎是一个拼写错误可能不是运行时的拼写错误.在Ruby中使用符号只是一种惯用且高效的方法.在像C这样的语言中,枚举实际上是解决这个问题的唯一方法.但是在C#中我看到很多人使用字符串来解决"命名索引"问题而不是花时间构建枚举.由于Ruby的符号*和*混淆了IDE的自动完成功能,因此字符串具有相同的"拼写错误编译"问题.
@yar:对于真正可用和可重构的Ruby(和类似语言),你确实需要出色的测试覆盖率.最简单的方法是永远不要添加功能,除了修复失败的测试用例.这将提供接近完整的覆盖范围,并且它完全改变了使用动态语言工作的体验.是的,我已经完成了一些非常严肃的Ruby代码重构.它们比Eclipse重构要困难一些,但通常代码行数要少得多,所以它平衡了.
@yar,没问题!但即使使用静态语言,我也倾向于选择非常全面的测试套件,因为我设计的API从测试套件内部到实际实现.因此对于那些采用这种风格的人来说,Ruby实际上没有额外的工作 - 我们也获得了一些很酷的动态功能.(根据我的经验,这有一些例外情况,特别是Haskell中的高阶数学代码,其中类型_and_测试非常宝贵.)祝Ruby好运!
好吧,既然ruby没有编译,我认为红宝石的方式是"约定优于配置",这意味着"我们硬编码东西,你猜我们硬编码了什么."

4> Alexey..:

我使用以下方法:

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

我喜欢它具有以下优点:

    它将视觉上的值分组为一个整体

    它执行一些编译时检查(与仅使用符号相比)

    我可以轻松访问所有可能值的列表:只是 MY_ENUM

    我可以轻松访问不同的值: MY_VALUE_1

    它可以具有任何类型的值,而不仅仅是符号

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


我认为这是最好的答案.功能,语法和最小代码开销最接近Java/C#.此外,您可以将定义嵌套到比一个级别更深的位置,并且仍然可以使用MyClass :: MY_ENUM.flatten恢复所有值.作为旁注,我将在这里使用大写的名称作为Ruby中常量的标准.MyClass :: MyEnum可能被误认为是对子类的引用.

5> vedant..:

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


如你所说 - 如果OP不使用Rails(或更准确地说对象不是ActiveRecord类型),则没有用.只是解释我的downvote就是全部.

6> dB...:

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



7> 小智..:

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



8> Oded Niv..:

我知道自从这个人发布这个问题以来已经有很长一段时间了,但是我有同样的问题而且这篇帖子没有给我答案.我想要一种简单的方法来查看数字代表什么,简单的比较,以及大多数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



9> Andrew Grimm..:

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

通常,您不必担心常量的拼写错误.如果拼错常量名称,通常会引发异常.



10> Daniel Lubar..:

也许最好的轻量级方法是

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

推荐阅读
帆侮听我悄悄说星星
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有