Apa cara terbaik untuk menerapkan idiom enum di Ruby? Saya mencari sesuatu yang bisa saya gunakan (hampir) seperti Java / C # enums.
Apa cara terbaik untuk menerapkan idiom enum di Ruby? Saya mencari sesuatu yang bisa saya gunakan (hampir) seperti Java / C # enums.
Jawaban:
Dua arah. Simbol ( :foo
notasi) atau konstanta ( FOO
notasi).
Simbol sesuai ketika Anda ingin meningkatkan keterbacaan tanpa mengotori kode dengan string literal.
postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"
Konstanta sesuai ketika Anda memiliki nilai dasar yang penting. Hanya mendeklarasikan modul untuk memegang konstanta Anda dan kemudian mendeklarasikan konstanta di dalamnya.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
:minnesota.to_s
saat menyimpan ke database untuk menyimpan versi string simbol. Rails, saya percaya, memiliki beberapa metode penolong untuk menangani beberapa hal ini.
Saya terkejut bahwa tidak ada yang menawarkan sesuatu seperti berikut (diambil dari permata RAPI ):
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
Yang bisa digunakan seperti ini:
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
Contoh:
>> 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
Ini berfungsi baik dalam skenario basis data, atau ketika berhadapan dengan konstanta gaya C / enum (seperti halnya ketika menggunakan FFI , yang membuat penggunaan ekstensif oleh RAPI).
Selain itu, Anda tidak perlu khawatir tentang kesalahan ketik yang menyebabkan kegagalan diam, seperti halnya Anda menggunakan solusi tipe-hash.
Cara paling idiomatis untuk melakukan ini adalah dengan menggunakan simbol. Misalnya, alih-alih:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
... Anda bisa menggunakan simbol:
# 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)
Ini sedikit lebih terbuka daripada enum, tetapi cocok dengan semangat Ruby.
Simbol juga berkinerja sangat baik. Membandingkan dua simbol untuk kesetaraan, misalnya, jauh lebih cepat daripada membandingkan dua string.
Saya menggunakan pendekatan berikut:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
Saya suka untuk keuntungan berikut:
MY_ENUM
MY_VALUE_1
Simbol mungkin lebih baik karena Anda tidak perlu menulis nama kelas luar, jika Anda menggunakannya di kelas lain ( MyClass::MY_VALUE_1
)
Jika Anda menggunakan Rails 4.2 atau lebih tinggi, Anda dapat menggunakan Rails enum.
Rails sekarang memiliki enum secara default tanpa perlu menyertakan permata apa pun.
Ini sangat mirip (dan lebih banyak dengan fitur) dengan Java, C ++ enums.
Dikutip dari 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
kelas - saya percaya itu harus memungkinkan hanya 1 instance.
Ini adalah pendekatan saya untuk enums di Ruby. Saya ingin pendek dan manis, belum tentu yang paling seperti C. Adakah pikiran?
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
Lihatlah permata 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
Mungkin pendekatan ringan terbaik adalah
module MyConstants
ABC = Class.new
DEF = Class.new
GHI = Class.new
end
Nilai cara ini memiliki nama terkait, seperti dalam Java / C #:
MyConstants::ABC
=> MyConstants::ABC
Untuk mendapatkan semua nilai, Anda bisa melakukannya
MyConstants.constants
=> [:ABC, :DEF, :GHI]
Jika Anda ingin nilai ordinal enum, Anda bisa melakukannya
MyConstants.constants.index :GHI
=> 2
class ABC; end
Saya tahu sudah lama sejak orang itu memposting pertanyaan ini, tetapi saya memiliki pertanyaan yang sama dan posting ini tidak memberi saya jawabannya. Saya ingin cara mudah untuk melihat apa yang mewakili angka, perbandingan mudah, dan sebagian besar dari semua dukungan ActiveRecord untuk pencarian menggunakan kolom yang mewakili enum.
Saya tidak menemukan apa pun, jadi saya membuat implementasi yang luar biasa yang disebut yinum yang memungkinkan semua yang saya cari. Membuat banyak spesifikasi, jadi saya cukup yakin itu aman.
Beberapa contoh fitur:
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
Jika Anda khawatir tentang kesalahan ketik dengan simbol, pastikan kode Anda menimbulkan pengecualian saat Anda mengakses nilai dengan kunci yang tidak ada. Anda dapat melakukan ini dengan menggunakan fetch
daripada []
:
my_value = my_hash.fetch(:key)
atau dengan membuat hash meningkatkan pengecualian secara default jika Anda memberikan kunci yang tidak ada:
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
Jika hash sudah ada, Anda bisa menambahkan perilaku peningkatan pengecualian:
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
Biasanya, Anda tidak perlu khawatir tentang keamanan kesalahan ketik dengan konstanta. Jika Anda salah mengeja nama konstan, biasanya akan muncul pengecualian.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
ini mendefinisikan simbol kunci. missing
, something
, Dll, dan juga membuat mereka sebanding melalui nilai-nilai terkait.)
Seseorang pergi ke depan dan menulis permata ruby bernama Renum . Ia mengklaim untuk mendapatkan perilaku Java / C # terdekat. Secara pribadi saya masih belajar Ruby, dan saya sedikit terkejut ketika saya ingin membuat kelas tertentu berisi enum statis, mungkin hash, bahwa itu tidak mudah ditemukan melalui google.
Itu semua tergantung bagaimana Anda menggunakan Java atau C # enums. Cara Anda menggunakannya akan menentukan solusi yang akan Anda pilih di Ruby.
Coba Set
jenis asli , misalnya:
>> 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]
?
Baru-baru ini kami merilis permata yang mengimplementasikan Enums di Ruby . Dalam posting saya, Anda akan menemukan jawaban atas pertanyaan Anda. Juga saya jelaskan di sana mengapa implementasi kami lebih baik daripada yang sudah ada (sebenarnya ada banyak implementasi fitur ini di Ruby sebagai permata).
Solusi lain adalah menggunakan OpenStruct. Cukup lurus ke depan dan bersih.
https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html
Contoh:
# 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'
Simbol adalah cara batu delima. Namun, terkadang seseorang perlu berbicara dengan beberapa kode C atau sesuatu atau Java yang mengekspos beberapa enum untuk berbagai hal.
#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
Ini kemudian dapat digunakan seperti ini
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Ini tentu saja dapat dibuat abstrak dan Anda dapat memutar kelas Enum kami sendiri
server_Symb
huruf kedua dalam variabel (mis. ) Karena alasan tertentu? Kecuali jika ada alasan tertentu, itu menjadi idiomatis untuk variabel snake_case_with_all_lower_case
, dan simbol :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Tidak perlu i = 0
.
Saya telah menerapkan enum seperti itu
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
maka mudah untuk melakukan operasi
EnumType.ENUM_1.label
...
enum = EnumType.find_by_id 1
...
valueArray = EnumType.values
Ini sepertinya agak berlebihan, tetapi ini adalah metodologi yang telah saya gunakan beberapa kali, terutama di mana saya mengintegrasikan dengan xml atau semacamnya.
#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
Ini memberi saya ketelitian ac # enum dan itu terikat dengan model.
:VAL
. Akan lebih baik untuk memulai dengan sebuah array dan membangun hash menggunakan.map.with_index
.key
atau .invert
daripada :VAL
kunci ( stackoverflow.com/a/10989394/2208016 )
key
atauinvert
Kebanyakan orang menggunakan simbol (itulah :foo_bar
sintaks). Mereka semacam nilai-nilai buram yang unik. Simbol bukan milik tipe enum apa pun sehingga simbol tersebut tidak benar-benar mewakili tipe enum C, tetapi ini cukup bagus.
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
Keluaran:
1 - a
2 - b
3 - c
4 - d
to_enum
memberi Anda enumera tor , sedangkan enum
dalam C # / Java sense adalah enumera tion
Terkadang yang saya butuhkan adalah bisa mengambil nilai enum dan mengidentifikasi namanya mirip dengan dunia 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'
Ini bagi saya melayani tujuan enum dan menjaganya agar tetap dapat diperluas. Anda dapat menambahkan lebih banyak metode ke kelas Enum dan mendapatkan biola secara gratis di semua enum yang ditentukan. sebagai contoh. get_all_names dan semacamnya.
Pendekatan lain adalah dengan menggunakan kelas Ruby dengan hash yang berisi nama dan nilai seperti yang dijelaskan dalam posting blog RubyFleebie berikut . Ini memungkinkan Anda untuk mengkonversi dengan mudah antara nilai dan konstanta (terutama jika Anda menambahkan metode kelas untuk mencari nama untuk nilai yang diberikan).
Saya pikir cara terbaik untuk mengimplementasikan enumerasi seperti jenis adalah dengan simbol karena cukup banyak berperilaku sebagai integer (ketika datang ke performace, object_id digunakan untuk membuat perbandingan); Anda tidak perlu khawatir tentang pengindeksan dan mereka terlihat sangat rapi dalam kode xD Anda
Cara lain untuk meniru enum dengan penanganan kesetaraan yang konsisten (tanpa malu-malu diadopsi dari Dave Thomas). Mengizinkan enum terbuka (seperti simbol) dan enum tertutup (sudah ditentukan sebelumnya).
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
Coba 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
lihat lebih lanjut https://github.com/alfa-jpn/inum#usage