it-swarm.it

Enum in Ruby

Qual è il modo migliore per implementare l'enum idiom in Ruby? Sto cercando qualcosa che possa usare (quasi) come le enumerazioni Java/C #.

290
auramo

Due strade. Simboli (notazione :foo) o costanti (notazione FOO).

I simboli sono appropriati quando si desidera migliorare la leggibilità senza litterare il codice con stringhe letterali.

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

Le costanti sono appropriate quando hai un valore sottostante che è importante. Basta dichiarare un modulo per contenere le costanti e quindi dichiarare le costanti al suo interno.

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

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

Il modo più idiomatico per farlo è usare i simboli. Ad esempio, invece di:

enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

... puoi semplicemente usare i simboli:

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

Questo è un po 'più aperto dell'enum, ma si adatta bene allo spirito Ruby.

Anche i simboli si comportano molto bene. Ad esempio, il confronto di due simboli per l'uguaglianza è molto più rapido rispetto al confronto di due stringhe.

52
emk

Sono sorpreso che nessuno abbia offerto qualcosa del genere (raccolto dalla 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

Quale può essere usato così:

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

Esempio:

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

Questo funziona bene in scenari di database, o quando si tratta di costanti/enumerazioni in stile C (come nel caso in cui si usi FFI , che RAPI fa largo uso di).

Inoltre, non devi preoccuparti di errori di battitura che causano errori silenziosi, come faresti con l'uso di una soluzione di tipo hash.

52
Charles

Io uso il seguente approccio:

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

Mi piace per i seguenti vantaggi:

  1. Raggruppa i valori visivamente come un tutto
  2. Fa un po 'di tempo di compilazione (contrariamente all'uso dei simboli)
  3. Posso accedere facilmente all'elenco di tutti i valori possibili: solo MY_ENUM
  4. Posso accedere facilmente a valori distinti: MY_VALUE_1
  5. Può avere valori di qualsiasi tipo, non solo Symbol

I simboli possono essere migliori perché non devi scrivere il nome della classe esterna, se la stai usando in un'altra classe (MyClass::MY_VALUE_1)

30
Alexey

Se utilizzi Rails 4.2 o versioni successive, puoi utilizzare le enumerazioni di Rails.

Rails ora ha enumerazioni di default senza la necessità di includere gemme.

Questo è molto simile (e più con le caratteristiche) a enigmi Java, C++.

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

Questo è il mio approccio alle enumerazioni in Ruby. Stavo andando per corto e dolce, non necessariamente il più C-like. qualche idea?

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

So che è passato molto tempo da quando il ragazzo ha postato questa domanda, ma ho avuto la stessa domanda e questo post non mi ha dato la risposta. Volevo un modo semplice per vedere cosa rappresenta il numero, facile confronto e soprattutto supporto ActiveRecord per la ricerca usando la colonna che rappresenta l'enum.

Non ho trovato nulla, quindi ho realizzato un'impressionante implementazione chiamata yinum che permetteva tutto ciò che stavo cercando. Fatto tonnellate di specifiche, quindi sono abbastanza sicuro che sia sicuro.

Alcune caratteristiche di esempio:

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

Controlla la gemma di 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 sei preoccupato per errori di battitura con i simboli, assicurati che il tuo codice sollevi un'eccezione quando accedi a un valore con una chiave inesistente. Puoi farlo usando fetch piuttosto che []:

my_value = my_hash.fetch(:key)

o facendo in modo che l'hash generi un'eccezione per impostazione predefinita se si fornisce una chiave inesistente:

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 l'hash esiste già, puoi aggiungere un comportamento di eccezione:

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, non devi preoccuparti della sicurezza degli errori di battitura con le costanti. Se scrivi un nome errato, di solito genera un'eccezione.

5
Andrew Grimm

Qualcuno è andato avanti e ha scritto una gemma Ruby chiamata Renum . Sostiene di ottenere il comportamento più simile a Java/C #. Personalmente sto ancora imparando Ruby, e sono rimasto un po 'scioccato quando volevo fare in modo che una classe specifica contenesse un enum statico, forse un hash, che non fosse facilmente reperibile tramite google.

4
dlamblin

Forse il miglior approccio leggero sarebbe

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

In questo modo i valori hanno nomi associati, come in Java/C #:

MyConstants::ABC
=> MyConstants::ABC

Per ottenere tutti i valori, puoi farlo

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

Se vuoi il valore ordinale di un enum, puoi farlo

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

Recentemente abbiamo rilasciato un gem che implementa Enums in Ruby. Nel mio post troverete le risposte alle vostre domande. Inoltre ho descritto lì perché la nostra implementazione è migliore di quelle esistenti (in realtà ci sono molte implementazioni di questa funzionalità in Ruby come gemme). 

3
ka8725

Questo sembra un po 'superfluo, ma questa è una metodologia che ho usato un paio di volte, specialmente dove sto integrando con xml o qualcosa del genere.

#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

Questo mi dà il rigore di un c # enum ed è legato al modello.

2
jjk

I simboli sono il modo rubino. Tuttavia, a volte è necessario parlare con un codice C o qualcosa o Java che espone qualche enum per varie cose.


#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

Questo può quindi essere usato in questo modo


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

Ovviamente questo può essere reso astratto e puoi lanciare la nostra classe Enum 

2
Jonke

Ho implementato enumerazioni del genere 

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

quindi è facile da fare operazioni 

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values
2
Masuschi

Dipende tutto da come usi le enumerazioni Java o C #. Il modo in cui lo utilizzerai determinerà la soluzione che sceglierai in Ruby.

Prova il tipo Set nativo, ad esempio:

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

Un'altra soluzione sta usando OpenStruct. È piuttosto semplice e pulito.

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

Esempio:

# 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

La maggior parte delle persone usa i simboli (questa è la sintassi :foo_bar). Sono una specie di valori opachi unici. I simboli non appartengono a nessun tipo di enum in modo che non siano realmente una rappresentazione fedele del tipo enum di C, ma questo è praticamente il meglio che ottiene.

1
Jan Krüger

A volte tutto ciò di cui ho bisogno è di essere in grado di recuperare il valore di enum e identificare il suo nome simile al mondo di 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'

Questo per me serve allo scopo di enum e lo mantiene anche molto estensibile. Puoi aggiungere più metodi alla classe Enum e viola riceverli gratuitamente in tutte le enumerazioni definite. per esempio. get_all_names e cose del genere.

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

Produzione:

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)

Produzione:

GOOD
1
Hossein

Veloce e sporco, si sente come 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

Usalo come se usassi un Enum:

method_that_needs_options(FeelsLikeAnEnum.Option_1)
0
David Foley

Penso che il modo migliore per implementare i tipi di enumerazione sia con i simboli dato che praticamente si comportano come numeri interi (quando si tratta di performace, object_id è usato per fare confronti); non è necessario preoccuparsi dell'indicizzazione e appaiono davvero accurati nel codice xD

0
goreorto

Un altro approccio consiste nell'usare una classe Ruby con un hash contenente nomi e valori come descritto nel seguente RubyFleebie post del blog . Questo ti permette di convertire facilmente tra valori e costanti (specialmente se aggiungi un metodo di classe per cercare il nome di un dato valore).

0
Philippe Monnet

Un altro modo di imitare un enum con una gestione coerente delle uguaglianze (adottato spudoratamente da Dave Thomas). Consente l'enumerazione aperta (molto simile ai simboli) e le enumerazioni chiuse (predefinite).

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

Prova l'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

vedere di più https://github.com/alfa-jpn/inum#usage

0
horun