it-swarm-pt.tech

Enums em Ruby

Qual é a melhor maneira de implementar o idioma enum em Ruby? Estou procurando algo que eu possa usar (quase) como os enums Java/C #.

290
auramo

Dois caminhos. Símbolos (:foo notação) ou constantes (FOO notação).

Os símbolos são apropriados quando você deseja melhorar a legibilidade sem conter o código com strings literais.

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

Constantes são apropriadas quando você tem um valor subjacente que é importante. Apenas declare um módulo para manter suas constantes e, em seguida, declare as constantes dentro disso.

module Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end

flags = Foo::BAR | Foo::BAZ # flags = 3
289
mlibby

A maneira mais idiomática de fazer isso é usar símbolos. Por exemplo, em vez de:

enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

... você pode usar apenas símbolos:

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

Isso é um pouco mais aberto do que enums, mas se encaixa bem com o espírito do Ruby.

Os símbolos também funcionam muito bem. Comparar dois símbolos por igualdade, por exemplo, é muito mais rápido do que comparar duas strings.

52
emk

Estou surpreso que ninguém tenha oferecido algo como o seguinte (colhido do 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

Que pode ser usado assim:

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

Exemplo:

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

Isso funciona bem em cenários de banco de dados ou ao lidar com constantes/enums de estilo C (como é o caso ao usar FFI , que o RAPI faz uso extensivo).

Além disso, você não precisa se preocupar com erros de digitação que causam falhas silenciosas, como faria com o uso de uma solução do tipo hash.

52
Charles

Eu uso a seguinte abordagem:

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

Eu gosto das seguintes vantagens:

  1. Agrupa valores visualmente como um todo
  2. Ele faz uma verificação em tempo de compilação (em contraste com o uso de símbolos)
  3. Eu posso acessar facilmente a lista de todos os valores possíveis: apenas MY_ENUM
  4. Eu posso acessar facilmente valores distintos: MY_VALUE_1
  5. Pode ter valores de qualquer tipo, não apenas símbolo

Símbolos podem ser melhores porque você não tem que escrever o nome da classe externa, se você estiver usando em outra classe (MyClass::MY_VALUE_1)

30
Alexey

Se você estiver usando o Rails 4.2 ou superior, você pode usar as enums do Rails.

O Rails agora tem enums por padrão, sem a necessidade de incluir quaisquer gemas.

Isso é muito semelhante (e mais com recursos) para enums Java, C++.

Citado em 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
17
vedant

Esta é a minha abordagem para enums em Ruby. Eu estava indo para curto e doce, não necessariamente o mais C-like. Alguma ideia?

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

Eu sei que faz muito tempo que o cara postou essa pergunta, mas eu fiz a mesma pergunta e este post não me deu a resposta. Eu queria uma maneira fácil de ver o que o número representa, comparação fácil e, acima de tudo, suporte a ActiveRecord para pesquisa usando a coluna que representa o enum.

Eu não encontrei nada, então eu fiz uma implementação incrível chamada yinum que permitia tudo que eu estava procurando. Feito toneladas de especificações, então tenho certeza que é seguro.

Alguns recursos de exemplo:

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

Confira a gem 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
7
dB.

Se você estiver preocupado com erros de digitação com símbolos, verifique se o código gera uma exceção quando você acessa um valor com uma chave inexistente. Você pode fazer isso usando fetch em vez de []:

my_value = my_hash.fetch(:key)

ou fazendo o hash gerar uma exceção por padrão se você fornecer uma chave inexistente:

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

Se o hash já existir, você pode adicionar um comportamento de aumento de exceção:

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

Normalmente, você não precisa se preocupar com segurança de digitação com constantes. Se você digitar incorretamente um nome constante, ele normalmente gerará uma exceção.

5
Andrew Grimm

Alguém foi em frente e escreveu uma jóia Ruby chamada Renum . Pretende obter o comportamento semelhante a Java/C #. Pessoalmente, eu ainda estou aprendendo Ruby, e fiquei um pouco chocado quando quis fazer uma classe específica conter um enum estático, possivelmente um hash, que não era exatamente encontrado facilmente pelo google.

4
dlamblin

Talvez a melhor abordagem leve seria

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

Desta forma, os valores possuem nomes associados, como em Java/C #:

MyConstants::ABC
=> MyConstants::ABC

Para obter todos os valores, você pode fazer

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

Se você quer o valor ordinal de um enum, você pode fazer

MyConstants.constants.index :GHI
=> 2
4
Daniel Lubarov

Recentemente lançamos um gem que implementa Enums em Ruby. No meu post você encontrará as respostas nas suas perguntas. Também descrevi por que nossa implementação é melhor que as existentes (na verdade, existem muitas implementações desse recurso no Ruby ainda como gems). 

3
ka8725

Isso parece um pouco supérfluo, mas esta é uma metodologia que eu usei algumas vezes, especialmente onde eu estou integrando com xml ou algo assim.

#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

Isso me dá o rigor de um c # enum e está vinculado ao modelo.

2
jjk

Símbolos é o caminho do Ruby. No entanto, às vezes é preciso falar com algum código C ou algo ou Java que exponha algum enum para várias coisas.


#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

Isso pode ser usado assim


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

Isto é claro que pode ser feito abstrato e você pode rolar nossa própria classe Enum 

2
Jonke

Eu implementei enums assim 

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

então é fácil de fazer operações 

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values
2
Masuschi

Tudo depende de como você usa enums Java ou C #. O modo como você o usa ditará a solução que você escolherá no Ruby.

Experimente o tipo nativo Set, por exemplo:

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

Outra solução é usar o OpenStruct. É bem direto e limpo.

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

Exemplo:

# 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'
2
Roger

A maioria das pessoas usa símbolos (essa é a sintaxe :foo_bar). Eles são uma espécie de valores opacos únicos. Os símbolos não pertencem a nenhum tipo de estilo enum, portanto, eles não são realmente uma representação fiel do tipo enum de C, mas isso é praticamente o melhor possível.

1
Jan Krüger

Às vezes, tudo que preciso é ser capaz de buscar o valor do enum e identificar seu nome similar ao mundo do 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'

Isso serve para o propósito do enum e também o mantém muito extensível. Você pode adicionar mais métodos à classe Enum e viola obtê-los gratuitamente em todas as enums definidas. por exemplo. get_all_names e coisas assim.

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

Saída:

1 - a
2 - b
3 - c
4 - d

1
Anu
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)

Saída:

GOOD
1
Hossein

Rápido e sujo, parece c #:

class FeelsLikeAnEnum
  def self.Option_1() :option_1 end
  def self.Option_2() :option_2 end
  def self.Option_3() :option_3 end
end

Use como você usaria um Enum:

method_that_needs_options(FeelsLikeAnEnum.Option_1)
0
David Foley

Eu acho que a melhor maneira de implementar enumeração como tipos é com símbolos, uma vez que o comportamento se comporta como inteiro (quando se trata de performace, object_id é usado para fazer comparações); você não precisa se preocupar com indexação e eles parecem realmente limpos em seu código xD

0
goreorto

Outra abordagem é usar uma classe Ruby com um hash contendo nomes e valores, conforme descrito na seguinte postagem do blog RubyFleebie . Isso permite converter facilmente entre valores e constantes (especialmente se você adicionar um método de classe para procurar o nome de um determinado valor).

0
Philippe Monnet

Outra maneira de imitar um enum com um tratamento de igualdade consistente (adotado descaradamente por Dave Thomas). Permite enums abertos (muito parecidos com símbolos) e enums fechados (predefinidos).

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
0
Daniel Doubleday

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

veja mais https://github.com/alfa-jpn/inum#usage

0
horun