Domanda

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

È stato utile?

Soluzione

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

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

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

Le costanti sono appropriate quando si ha un valore sottostante importante.Dichiara semplicemente un modulo per contenere le tue costanti e poi dichiara le costanti al suo interno.

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

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

Altri suggerimenti

Sono sorpreso che nessuno abbia offerto qualcosa di simile al seguente (raccolto dal file RAPI gemma):

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

Che può essere usato in questo modo:

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 negli scenari di database o quando si ha a che fare con costanti/enumerazioni in stile C (come nel caso quando si utilizza FFI, di cui la RAPI fa ampio uso).

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

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 delle enumerazioni, ma si adatta bene allo spirito di Ruby.

Anche i simboli funzionano molto bene.Confrontare due simboli per l'uguaglianza, ad esempio, è molto più veloce che confrontare due stringhe.

Utilizzo il seguente approccio:

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

Mi piace per i seguenti vantaggi:

  1. Raggruppa visivamente i valori come un tutto unico
  2. Esegue alcuni controlli in fase di compilazione (in contrasto con l'utilizzo dei soli simboli)
  3. Posso accedere facilmente all'elenco di tutti i valori possibili:Appena MY_ENUM
  4. Posso facilmente accedere a valori distinti: MY_VALUE_1
  5. Può avere valori di qualsiasi tipo, non solo Symbol

I simboli potrebbero essere migliori perché non è necessario scrivere il nome della classe esterna, se la si utilizza in un'altra classe (MyClass::MY_VALUE_1)

Se stai utilizzando Rails 4.2 o versione successiva puoi utilizzare le enumerazioni Rails.

Rails ora ha enumerazioni per impostazione predefinita senza la necessità di includere gemme.

Questo è molto simile (e più con funzionalità) alle enumerazioni Java e 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

Questo è il mio approccio alle enumerazioni in Ruby.Volevo essere breve e dolce, non necessariamente il più in stile C.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

Dai un'occhiata alla gemma rubino-enum, https://github.com/dblock/ruby-enum.

class Gender
  include Enum

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

Gender.all
Gender::MALE

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

Non ho trovato nulla, quindi ho creato un'implementazione fantastica chiamata yinum che ha permesso tutto quello che cercavo.Ha fatto un sacco 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

Se sei preoccupato per gli 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 sollevi un'eccezione per impostazione predefinita se fornisci 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 raccolta delle eccezioni:

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 in ​​modo errato un nome costante, di solito verrà sollevata un'eccezione.

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'enumerazione, puoi farlo

MyConstants.constants.index :GHI
=> 2

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

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

Prova il nativo Set digitare, 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"}>

Recentemente abbiamo rilasciato a gemma che implementa Enumerazioni in Ruby.Nel mio inviare troverai le risposte alle tue domande.Inoltre ho descritto perché la nostra implementazione è migliore di quelle esistenti (in realtà ci sono molte implementazioni di questa funzionalità in Ruby ancora come gemme).

I simboli sono la via del rubino.Tuttavia, a volte è necessario parlare con del codice C o qualcosa del genere o Java che esponga alcune enumerazioni 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 utilizzato in questo modo


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

Questo ovviamente può essere reso astratto e puoi lanciare la nostra classe Enum

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

allora è facile eseguire le operazioni

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values

Sembra un po' superfluo, ma questa è una metodologia che ho usato alcune volte, soprattutto dove mi 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 enum c# ed è legato al modello.

Un'altra soluzione sta utilizzando 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'

La maggior parte delle persone usa i simboli (questo è il :foo_bar sintassi).Sono una sorta di valori opachi unici.I simboli non appartengono a nessun tipo di stile enum, quindi non sono una rappresentazione fedele del tipo enum di C, ma questo è più o meno il massimo.

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

Produzione:

GOOD

A volte tutto ciò di cui ho bisogno è poter recuperare il valore di enum e identificare il suo nome in modo simile al mondo 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 ottenerli gratuitamente in tutte le enumerazioni definite.Per esempio.get_all_names e cose del genere.

Un altro approccio consiste nell'utilizzare una classe Ruby con un hash contenente nomi e valori come descritto di seguito Post del blog RubyFleebie.Ciò ti consente di convertire facilmente tra valori e costanti (specialmente se aggiungi un metodo di classe per cercare il nome di un determinato valore).

Penso che il modo migliore per implementare tipi simili a enumerazioni sia con i simboli poiché praticamente si comportano come numeri interi (quando si tratta di prestazioni, object_id viene utilizzato per effettuare confronti);non devi preoccuparti dell'indicizzazione e sembrano davvero ordinati nel tuo codice xD

Un altro modo per imitare un'enumerazione con una gestione coerente dell'uguaglianza (adottato spudoratamente da Dave Thomas).Consente enumerazioni aperte (molto simili ai simboli) ed 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

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

Vedi altro https://github.com/alfa-jpn/inum#usage

Veloce e sporco, sembra 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 useresti un Enum:

method_that_needs_options(FeelsLikeAnEnum.Option_1)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top