Enum deyimini Ruby'de uygulamanın en iyi yolu nedir? Java / C # enums gibi (neredeyse) kullanabileceğim bir şey arıyorum.
Enum deyimini Ruby'de uygulamanın en iyi yolu nedir? Java / C # enums gibi (neredeyse) kullanabileceğim bir şey arıyorum.
Yanıtlar:
İki yol. Semboller ( :foo
gösterim) veya sabitler ( FOO
gösterim).
Değişmez dizelerle kodun silinmesine gerek kalmadan okunabilirliği artırmak istediğinizde semboller uygundur.
postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"
Önemli olan temel bir değeriniz olduğunda sabitler uygundur. Sadece sabitlerinizi tutmak için bir modül tanımlayın ve ardından bu sabitleri sabitleyin.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
:minnesota.to_s
, sembolün dize sürümünü kaydetmek için bir veritabanına kaydederken olduğu gibi bir şey kullanabilirsiniz . Raylar, sanırım, bununla başa çıkmak için bazı yardımcı yöntemler var.
Kimsenin ( RAPI geminden hasat edilmiş) aşağıdaki gibi bir şey sunmadığına şaşırdım :
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
Hangi gibi kullanılabilir:
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
Misal:
>> 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
Bu, veritabanı senaryolarında veya C stili sabitler / numaralandırmalarla uğraşırken iyi çalışır ( RAPI'nın kapsamlı olarak kullandığı FFI kullanılırken olduğu gibi).
Ayrıca, karma türü bir çözüm kullanarak yaptığınız gibi sessiz hatalara neden olan yazım hataları hakkında endişelenmenize gerek yoktur.
Bunu yapmanın en deyimsel yolu sembolleri kullanmaktır. Örneğin:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
... sadece sembolleri kullanabilirsiniz:
# 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)
Bu numaralardan daha açık uçlu, ama Yakut ruhuna iyi uyuyor.
Semboller de çok iyi performans gösterir. Eşitlik için iki simgeyi karşılaştırmak, örneğin, iki dizeyi karşılaştırmaktan çok daha hızlıdır.
Aşağıdaki yaklaşımı kullanıyorum:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
Aşağıdaki avantajlar için beğendim:
MY_ENUM
MY_VALUE_1
Semboller daha iyi olabilir, çünkü başka bir sınıfta ( MyClass::MY_VALUE_1
) kullanıyorsanız, dış sınıfın adını yazmak zorunda kalmazsınız
Rails 4.2 veya daha üstünü kullanıyorsanız Rails numaralarını kullanabilirsiniz.
Rails artık varsayılan olarak herhangi bir mücevher eklemeye gerek kalmadan numaralandırmalara sahip.
Bu, Java, C ++ numaralarına çok benzer (ve daha fazla özellik ile).
Alıntı: 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
sınıfın birçok örneğini oluşturmasına izin verdiği için sevmiyorum - sadece 1 örneğe izin vermesi gerektiğine inanıyorum.
Bu Ruby'deki numaralandırmalara yaklaşımım. Kısa ve tatlı olacaktım, mutlaka en C benzeri değil. Düşüncesi olan var mı?
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
Ruby-enum gemine göz atın, https://github.com/dblock/ruby-enum .
class Gender
include Enum
Gender.define :MALE, "male"
Gender.define :FEMALE, "female"
end
Gender.all
Gender::MALE
Belki de en iyi hafif yaklaşım
module MyConstants
ABC = Class.new
DEF = Class.new
GHI = Class.new
end
Bu şekilde değerler, Java / C # 'da olduğu gibi ilişkili adlara sahiptir:
MyConstants::ABC
=> MyConstants::ABC
Tüm değerleri elde etmek için şunları yapabilirsiniz:
MyConstants.constants
=> [:ABC, :DEF, :GHI]
Bir enum'un sıralı değerini istiyorsanız,
MyConstants.constants.index :GHI
=> 2
class ABC; end
Adamın bu soruyu yayınlamasından bu yana uzun zaman geçtiğini biliyorum, ama aynı soruyu sordum ve bu yazı bana cevap vermedi. Numarayı temsil eden sütun, kolay karşılaştırma ve enum temsil eden sütunu kullanarak arama için tüm ActiveRecord desteği görmek için kolay bir yol istedim.
Hiçbir şey bulamadım, bu yüzden aradığım her şeye izin veren yinum adlı harika bir uygulama yaptım. Tonlarca spesifikasyon yaptı, bu yüzden güvenli olduğundan eminim.
Bazı örnek özellikler:
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
Sembollü yazım hataları hakkında endişeleniyorsanız, var olmayan bir anahtarla bir değere eriştiğinizde kodunuzun bir istisna oluşturduğundan emin olun. Bunu aşağıdakileri fetch
yerine kullanarak yapabilirsiniz []
:
my_value = my_hash.fetch(:key)
veya varolmayan bir anahtar sağlarsanız karma değerini varsayılan olarak bir istisna oluşturarak:
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
Karma zaten varsa, özel durum oluşturma davranışı ekleyebilirsiniz:
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
Normalde, sabitlerle yazım güvenliği konusunda endişelenmenize gerek yoktur. Sabit bir adı yanlış yazarsanız, genellikle bir istisna oluşturur.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
Bu tanımlar anahtar semboller. missing
, something
Vb ve ayrıca ilişkili değerlerin aracılığıyla onları karşılaştırılabilir hale getirir.)
Birisi devam etti ve Renum adlı yakut bir mücevher yazdı . En yakın Java / C # benzeri davranışı elde ettiğini iddia ediyor. Şahsen hala Ruby öğreniyorum ve belirli bir sınıfı statik bir numaralandırma, muhtemelen bir karma içeren, google üzerinden tam olarak kolayca bulunamadığını yapmak istediğimde biraz şok oldum.
Her şey Java veya C # numaralarını nasıl kullandığınıza bağlıdır. Nasıl kullanacağınız Ruby'de seçeceğiniz çözümü dikte edecektir.
Yerel Set
türü deneyin, örneğin:
>> 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]
?
Son zamanlarda Ruby'de Enums'ı uygulayan bir mücevher çıkardık . Benim içinde yazı sorularınızın cevapları bulacaksınız. Ayrıca, uygulamamızın neden mevcut olanlardan daha iyi olduğunu açıkladım (aslında bu özelliğin Ruby'de henüz mücevher olarak birçok uygulaması var).
Başka bir çözüm OpenStruct kullanıyor. Onun oldukça düz ileri ve temiz.
https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html
Misal:
# 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'
Semboller yakut yoldur. Bununla birlikte, bazen bazı şeyler için bazı numaralar ortaya çıkaran bir C kodu veya bir şey veya Java ile konuşmanız gerekir.
#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
Bu daha sonra şu şekilde kullanılabilir
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Bu elbette soyut yapılabilir ve kendi Enum sınıfımızı yuvarlayabilirsiniz
server_Symb
Belirli bir nedenden ötürü (örn. ) Değişkenlerdeki ikinci kelimeyi büyük yazıyor musunuz? Belirli bir sebep olmadıkça, değişkenlerin snake_case_with_all_lower_case
ve sembollerin deyimi deyimdir :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Gerek yok i = 0
.
Bunun gibi numaralandırmalar uyguladım
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
o zaman kolay işlemler
EnumType.ENUM_1.label
...
enum = EnumType.find_by_id 1
...
valueArray = EnumType.values
Bu biraz gereksiz görünüyor, ancak bu, özellikle xml veya bazılarıyla entegre olduğum yerlerde birkaç kez kullandığım bir metodolojidir.
#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
Bu bana ac # enum katılığını verir ve modele bağlıdır.
:VAL
. Bir dizi ile başlamak ve karma kullanarak oluşturmak daha iyi olurdu.map.with_index
.key
veya anahtar .invert
yerine ters arama yapmaya eğilimliyim :VAL
( stackoverflow.com/a/10989394/2208016 )
key
veyainvert
Çoğu insan sembol kullanır (bu :foo_bar
sözdizimidir). Bir çeşit benzersiz opak değerlerdir. Semboller herhangi bir enum tarzı tipe ait değildir, bu yüzden C'nin enum tipinin gerçekten sadık bir temsili değildir, ancak bu oldukça iyi.
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
Çıktı:
1 - a
2 - b
3 - c
4 - d
to_enum
size bir enumera verir tor oysa enum
C # / Java anlamda bir enumera olan siyon
Bazen ihtiyacım olan tek şey enum'un değerini getirebilmek ve adını java dünyasına benzer şekilde tanımlayabilmek.
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'
Bu benim için sayım amacına hizmet eder ve onu da genişletilebilir tutar. Enum sınıfına daha fazla yöntem ekleyebilir ve viyolaları tanımlanmış tüm numaralandırmalarda ücretsiz olarak alabilirsiniz. Örneğin. get_all_names ve bunun gibi şeyler.
Başka bir yaklaşım, aşağıdaki RubyFleebie blog gönderisinde açıklandığı gibi, adlar ve değerler içeren bir karma içeren bir Ruby sınıfı kullanmaktır . Bu, değerler ve sabitler arasında kolayca dönüştürmenize olanak tanır (özellikle belirli bir değerin adını aramak için bir sınıf yöntemi eklerseniz).
Türleri gibi numaralandırma uygulamak için en iyi yolu semboller ile olduğunu düşünüyorum çünkü hemen hemen tamsayı gibi davranır (performans söz konusu olduğunda, object_id karşılaştırma yapmak için kullanılır); indeksleme konusunda endişelenmenize gerek yok ve kodunuz xD'de gerçekten düzgün görünüyorlar
Tutarlı eşitlik yönetimi ile bir numaralandırmayı taklit etmenin başka bir yolu (Dave Thomas'tan utanmadan benimsenmiştir). Açık numaralandırmaya (semboller gibi) ve kapalı (önceden tanımlanmış) numaralara izin verir.
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
Inum'u deneyin. 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
daha fazla bilgi için https://github.com/alfa-jpn/inum#usage