Was ist der beste Weg, um die Enum-Sprache in Ruby zu implementieren? Ich suche etwas, das ich (fast) wie die Java / C # -Aufzählungen verwenden kann.
Was ist der beste Weg, um die Enum-Sprache in Ruby zu implementieren? Ich suche etwas, das ich (fast) wie die Java / C # -Aufzählungen verwenden kann.
Antworten:
Zwei Wege. Symbole ( :foo
Notation) oder Konstanten ( FOO
Notation).
Symbole sind geeignet, wenn Sie die Lesbarkeit verbessern möchten, ohne Code mit Literalzeichenfolgen zu verunreinigen.
postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"
Konstanten sind angemessen, wenn Sie einen wichtigen zugrunde liegenden Wert haben. Deklarieren Sie einfach ein Modul, das Ihre Konstanten enthält, und deklarieren Sie dann die darin enthaltenen Konstanten.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
:minnesota.to_s
beim Speichern in einer Datenbank verwenden, um die Zeichenfolgenversion des Symbols zu speichern. Ich glaube, Rails hat einige Hilfsmethoden, um mit einigen davon umzugehen.
Ich bin überrascht, dass niemand so etwas angeboten hat (geerntet aus dem RAPI- Juwel):
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
Welches kann so verwendet werden:
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
Beispiel:
>> 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
Dies funktioniert gut in Datenbankszenarien oder beim Umgang mit Konstanten / Aufzählungen im C-Stil (wie dies bei der Verwendung von FFI der Fall ist , das RAPI in großem Umfang verwendet).
Außerdem müssen Sie sich keine Sorgen über Tippfehler machen, die stille Fehler verursachen, wie Sie es bei der Verwendung einer Hash-Lösung tun würden.
Der idiomatischste Weg, dies zu tun, ist die Verwendung von Symbolen. Zum Beispiel anstelle von:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
... Sie können nur Symbole verwenden:
# 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)
Dies ist etwas offener als Aufzählungen, passt aber gut zum Ruby-Geist.
Symbole funktionieren auch sehr gut. Das Vergleichen von zwei Symbolen auf Gleichheit ist beispielsweise viel schneller als das Vergleichen von zwei Zeichenfolgen.
Ich benutze den folgenden Ansatz:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
Ich mag es für die folgenden Vorteile:
MY_ENUM
MY_VALUE_1
Symbole sind möglicherweise besser, da Sie den Namen der äußeren Klasse nicht schreiben müssen, wenn Sie ihn in einer anderen Klasse verwenden ( MyClass::MY_VALUE_1
)
Wenn Sie Rails 4.2 oder höher verwenden, können Sie Rails-Aufzählungen verwenden.
Rails hat jetzt standardmäßig Aufzählungen, ohne dass Edelsteine eingefügt werden müssen.
Dies ist Java, C ++ - Enums sehr ähnlich (und mehr mit Funktionen).
Zitiert von 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
Conversation
Klasse zu erstellen - ich glaube, es darf nur eine Instanz zulassen.
Dies ist meine Herangehensweise an Aufzählungen in Ruby. Ich war kurz und bündig, nicht unbedingt der C-artigste. Irgendwelche Gedanken?
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
Schauen Sie sich das Rubin-Enum-Juwel https://github.com/dblock/ruby-enum an .
class Gender
include Enum
Gender.define :MALE, "male"
Gender.define :FEMALE, "female"
end
Gender.all
Gender::MALE
Vielleicht wäre der beste leichte Ansatz
module MyConstants
ABC = Class.new
DEF = Class.new
GHI = Class.new
end
Auf diese Weise haben Werte Namen wie in Java / C #:
MyConstants::ABC
=> MyConstants::ABC
Um alle Werte zu erhalten, können Sie dies tun
MyConstants.constants
=> [:ABC, :DEF, :GHI]
Wenn Sie den Ordnungswert einer Aufzählung möchten, können Sie dies tun
MyConstants.constants.index :GHI
=> 2
class ABC; end
Ich weiß, es ist lange her, dass der Typ diese Frage gestellt hat, aber ich hatte die gleiche Frage und dieser Beitrag gab mir keine Antwort. Ich wollte eine einfache Möglichkeit, um zu sehen, was die Zahl darstellt, einen einfachen Vergleich und vor allem ActiveRecord-Unterstützung für die Suche anhand der Spalte, die die Aufzählung darstellt.
Ich habe nichts gefunden, also habe ich eine großartige Implementierung namens yinum gemacht, die alles erlaubte, was ich suchte. Ich habe eine Menge Spezifikationen erstellt, daher bin ich mir ziemlich sicher, dass es sicher ist.
Einige Beispielfunktionen:
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
Wenn Sie sich Sorgen über Tippfehler mit Symbolen machen, stellen Sie sicher, dass Ihr Code eine Ausnahme auslöst, wenn Sie auf einen Wert mit einem nicht vorhandenen Schlüssel zugreifen. Sie können dies tun, indem Sie fetch
anstatt []
:
my_value = my_hash.fetch(:key)
oder indem der Hash standardmäßig eine Ausnahme auslöst, wenn Sie einen nicht vorhandenen Schlüssel angeben:
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
Wenn der Hash bereits vorhanden ist, können Sie das Verhalten zum Auslösen von Ausnahmen hinzufügen:
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
Normalerweise müssen Sie sich keine Sorgen um die Tippfehlersicherheit mit Konstanten machen. Wenn Sie einen konstanten Namen falsch schreiben, wird normalerweise eine Ausnahme ausgelöst.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
Dies definiert die Tastensymbole. missing
, something
Usw., und mache auch vergleichbar sie über die zugehörigen Werte.)
Jemand ging voran und schrieb einen Rubinstein namens Renum . Es wird behauptet, das nächstgelegene Java / C # -ähnliche Verhalten zu erhalten. Persönlich lerne ich immer noch Ruby und war ein wenig schockiert, als ich wollte, dass eine bestimmte Klasse eine statische Aufzählung enthält, möglicherweise einen Hash, der über Google nicht so einfach zu finden ist.
Es hängt alles davon ab, wie Sie Java- oder C # -Aufzählungen verwenden. Wie Sie es verwenden, bestimmt die Lösung, die Sie in Ruby auswählen.
Probieren Sie den nativen Set
Typ aus, zum Beispiel:
>> 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"}>
Set[:a, :b, :c]
?
Kürzlich haben wir eine veröffentlicht Juwel veröffentlicht , das Enums in Ruby implementiert . In meinem Beitrag finden Sie die Antworten auf Ihre Fragen. Außerdem habe ich dort beschrieben, warum unsere Implementierung besser ist als die vorhandenen (tatsächlich gibt es viele Implementierungen dieser Funktion in Ruby noch als Juwelen).
Eine andere Lösung ist die Verwendung von OpenStruct. Es ist ziemlich einfach und sauber.
https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html
Beispiel:
# 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'
Symbole ist der Rubinweg. Manchmal muss man jedoch mit C-Code oder etwas anderem oder Java sprechen, das eine Aufzählung für verschiedene Dinge verfügbar macht.
#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
Dies kann dann so verwendet werden
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Dies kann natürlich abstrakt gemacht werden und Sie können unsere eigene Enum-Klasse würfeln
server_Symb
) aus einem bestimmten Grund? Sofern es keinen bestimmten Grund gibt, ist es idiomatisch für Variablen snake_case_with_all_lower_case
und für Symbole :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Keine Notwendigkeit für i = 0
.
Ich habe solche Aufzählungen implementiert
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
dann ist es einfach, Operationen durchzuführen
EnumType.ENUM_1.label
...
enum = EnumType.find_by_id 1
...
valueArray = EnumType.values
Dies scheint ein bisschen überflüssig zu sein, aber dies ist eine Methode, die ich einige Male verwendet habe, insbesondere wenn ich in XML oder ähnliches integriere.
#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
Dies gibt mir die Strenge von ac # enum und es ist an das Modell gebunden.
:VAL
. Es wäre besser, mit einem Array zu beginnen und den Hash mit.map.with_index
.key
oder .invert
anstelle eines :VAL
Schlüssels durchführen ( stackoverflow.com/a/10989394/2208016 )
key
invert
Die meisten Leute benutzen Symbole (das ist die :foo_bar
Syntax). Sie sind eine Art einzigartige undurchsichtige Werte. Symbole gehören zu keinem Aufzählungstyp, daher sind sie keine getreue Darstellung des Aufzählungstyps von C, aber das ist so gut wie es nur geht.
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
Ausgabe:
1 - a
2 - b
3 - c
4 - d
to_enum
gibt Ihnen ein enumera tor , während enum
in der C # / Java Sinn ist ein enumera tion
Manchmal ist alles, was ich brauche, in der Lage zu sein, den Wert von enum abzurufen und seinen Namen ähnlich wie in der Java-Welt zu identifizieren.
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'
Dies dient für mich dem Zweck der Aufzählung und hält es auch sehr erweiterbar. Sie können der Enum-Klasse weitere Methoden hinzufügen, und Viola erhält sie in allen definierten Enums kostenlos. zum Beispiel. get_all_names und ähnliches.
Ein anderer Ansatz besteht darin, eine Ruby-Klasse mit einem Hash zu verwenden, der Namen und Werte enthält, wie im folgenden RubyFleebie-Blogbeitrag beschrieben . Auf diese Weise können Sie problemlos zwischen Werten und Konstanten konvertieren (insbesondere, wenn Sie eine Klassenmethode hinzufügen, um den Namen für einen bestimmten Wert zu suchen).
Ich denke, der beste Weg, um Aufzählungstypen zu implementieren, sind Symbole, da sich diese so ziemlich wie Ganzzahlen verhalten (wenn es um die Leistung geht, wird object_id verwendet, um Vergleiche anzustellen). Sie müssen sich keine Gedanken über die Indizierung machen und sie sehen in Ihrem Code xD wirklich gut aus
Eine andere Möglichkeit, eine Aufzählung mit konsequenter Gleichstellungsbehandlung nachzuahmen (schamlos von Dave Thomas übernommen). Ermöglicht offene Aufzählungen (ähnlich wie Symbole) und geschlossene (vordefinierte) Aufzählungen.
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
Probieren Sie das 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
Weitere Informationen finden Sie unter https://github.com/alfa-jpn/inum#usage