Cách tốt nhất để thực hiện thành ngữ enum trong Ruby là gì? Tôi đang tìm kiếm thứ gì đó mà tôi có thể sử dụng (gần như) như enum Java / C #.
Cách tốt nhất để thực hiện thành ngữ enum trong Ruby là gì? Tôi đang tìm kiếm thứ gì đó mà tôi có thể sử dụng (gần như) như enum Java / C #.
Câu trả lời:
Hai lối. Biểu tượng (:foo
ký hiệu) hoặc hằng số ( FOO
ký hiệu).
Các biểu tượng phù hợp khi bạn muốn tăng cường khả năng đọc mà không cần vứt mã bằng các chuỗi ký tự.
postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"
Hằng số thích hợp khi bạn có một giá trị cơ bản quan trọng. Chỉ cần khai báo một mô-đun để giữ các hằng số của bạn và sau đó khai báo các hằng số trong đó.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
:minnesota.to_s
khi lưu vào cơ sở dữ liệu để lưu phiên bản chuỗi của biểu tượng. Rails, tôi tin rằng, có một số phương pháp trợ giúp để đối phó với một số điều này.
Tôi ngạc nhiên rằng không ai đã cung cấp một cái gì đó như sau (được thu hoạch từ đá quý 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
Mà có thể được sử dụng như vậy:
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
Thí dụ:
>> 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
Điều này chơi tốt trong các kịch bản cơ sở dữ liệu hoặc khi xử lý các hằng số / enum kiểu C (như trường hợp khi sử dụng FFI , RAPI sử dụng rộng rãi).
Ngoài ra, bạn không phải lo lắng về lỗi chính tả gây ra lỗi thất bại, giống như bạn sử dụng giải pháp loại băm.
Cách thành ngữ nhất để làm điều này là sử dụng các biểu tượng. Ví dụ: thay vì:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
... bạn chỉ có thể sử dụng các biểu tượng:
# 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)
Đây là một kết thúc mở hơn một chút so với enums, nhưng nó rất phù hợp với tinh thần Ruby.
Biểu tượng cũng thực hiện rất tốt. So sánh hai biểu tượng cho sự bình đẳng, ví dụ, nhanh hơn nhiều so với so sánh hai chuỗi.
Tôi sử dụng cách tiếp cận sau:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
Tôi thích nó vì những lợi thế sau:
MY_ENUM
MY_VALUE_1
Các biểu tượng có thể tốt hơn vì bạn không phải viết tên của lớp bên ngoài, nếu bạn đang sử dụng nó trong một lớp khác ( MyClass::MY_VALUE_1
)
Nếu bạn đang sử dụng Rails 4.2 trở lên, bạn có thể sử dụng Rails enums.
Rails bây giờ có enums theo mặc định mà không cần bao gồm bất kỳ đá quý.
Điều này rất giống với các tính năng của Java, C ++.
Trích dẫn từ http://edgeapi.rubyonrails.org/groupes/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
lớp - tôi tin rằng nó chỉ cho phép 1 thể hiện.
Đây là cách tiếp cận của tôi với enums trong Ruby. Tôi đã đi ngắn và ngọt ngào, không nhất thiết phải giống như C nhất. Có suy nghĩ gì không?
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
Kiểm tra đá quý 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
Có lẽ cách tiếp cận nhẹ nhất sẽ là
module MyConstants
ABC = Class.new
DEF = Class.new
GHI = Class.new
end
Cách này có giá trị có tên liên quan, như trong Java / C #:
MyConstants::ABC
=> MyConstants::ABC
Để có được tất cả các giá trị, bạn có thể làm
MyConstants.constants
=> [:ABC, :DEF, :GHI]
Nếu bạn muốn giá trị thứ tự của enum, bạn có thể làm
MyConstants.constants.index :GHI
=> 2
class ABC; end
Tôi biết đã lâu lắm rồi anh chàng mới đăng câu hỏi này, nhưng tôi cũng có câu hỏi tương tự và bài đăng này không cho tôi câu trả lời. Tôi muốn một cách dễ dàng để xem con số đại diện cho điều gì, so sánh dễ dàng và hầu hết tất cả hỗ trợ ActiveRecord để tra cứu bằng cách sử dụng cột đại diện cho enum.
Tôi đã không tìm thấy bất cứ điều gì, vì vậy tôi đã thực hiện một triển khai tuyệt vời gọi là yinum cho phép mọi thứ tôi đang tìm kiếm. Được thực hiện rất nhiều thông số kỹ thuật, vì vậy tôi khá chắc chắn rằng nó an toàn.
Một số tính năng ví dụ:
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
Nếu bạn lo lắng về lỗi chính tả với các ký hiệu, hãy đảm bảo mã của bạn tăng ngoại lệ khi bạn truy cập một giá trị bằng khóa không tồn tại. Bạn có thể làm điều này bằng cách sử dụng fetch
chứ không phải []
:
my_value = my_hash.fetch(:key)
hoặc bằng cách làm cho hàm băm tăng một ngoại lệ theo mặc định nếu bạn cung cấp khóa không tồn tại:
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
Nếu hàm băm đã tồn tại, bạn có thể thêm vào hành vi tăng ngoại lệ:
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
Thông thường, bạn không phải lo lắng về sự an toàn của lỗi đánh máy với các hằng số. Nếu bạn viết sai một tên không đổi, nó thường sẽ đưa ra một ngoại lệ.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
định nghĩa này những biểu tượng chủ chốt. missing
, something
Vv, và cũng có thể làm cho họ có thể so sánh qua các giá trị liên quan.)
Ai đó đã đi trước và viết một viên đá quý ruby tên là Renum . Nó tuyên bố để có được hành vi giống như Java / C # gần nhất. Cá nhân tôi vẫn đang học Ruby, và tôi đã hơi sốc khi tôi muốn tạo một lớp cụ thể chứa một enum tĩnh, có thể là hàm băm, mà nó không thể tìm thấy chính xác qua google.
Tất cả phụ thuộc vào cách bạn sử dụng enum Java hoặc C #. Cách bạn sử dụng sẽ quyết định giải pháp bạn sẽ chọn trong Ruby.
Hãy thử Set
loại bản địa , ví dụ:
>> 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]
?
Gần đây, chúng tôi đã phát hành một viên đá quý thực hiện Enums trong Ruby . Trong bài viết của tôi, bạn sẽ tìm thấy câu trả lời cho câu hỏi của bạn. Ngoài ra tôi đã mô tả ở đó lý do tại sao việc triển khai của chúng tôi tốt hơn so với hiện tại (thực tế có nhiều triển khai tính năng này trong Ruby nhưng là đá quý).
Một giải pháp khác là sử dụng OpenStabase. Nó khá thẳng về phía trước và sạch sẽ.
https://ruby-doc.org/stdlib-2.3.1/libdoc/osturation/rdoc/OpenSturation.html
Thí dụ:
# 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'
Biểu tượng là cách ruby. Tuy nhiên, đôi khi người ta cần nói chuyện với một số mã C hoặc một cái gì đó hoặc Java để lộ một số enum cho những thứ khác nhau.
#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
Điều này sau đó có thể được sử dụng như thế này
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Điều này tất nhiên có thể được thực hiện trừu tượng và bạn có thể cuộn lớp Enum của riêng chúng tôi
server_Symb
) cho một lý do cụ thể không? Trừ khi có một lý do cụ thể, nó là thành ngữ cho các biến snake_case_with_all_lower_case
và cho các ký hiệu là :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Không cần i = 0
.
Tôi đã thực hiện enum như thế
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
sau đó thật dễ dàng để thực hiện các hoạt động
EnumType.ENUM_1.label
...
enum = EnumType.find_by_id 1
...
valueArray = EnumType.values
Điều này có vẻ hơi thừa, nhưng đây là một phương pháp mà tôi đã sử dụng một vài lần, đặc biệt là khi tôi đang tích hợp với xml hoặc một số như vậy.
#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
Điều này mang lại cho tôi sự nghiêm ngặt của ac # enum và nó được gắn với mô hình.
:VAL
. Sẽ tốt hơn khi bắt đầu với một mảng và xây dựng hàm băm bằng cách sử dụng.map.with_index
.key
hoặc .invert
thay vì một :VAL
khóa ( stackoverflow.com/a/10989394/2208016 )
key
hoặcinvert
Hầu hết mọi người sử dụng các ký hiệu (đó là :foo_bar
cú pháp). Chúng là những giá trị mờ đục độc đáo. Các biểu tượng không thuộc bất kỳ loại enum nào vì vậy chúng không thực sự là đại diện trung thành của loại enum của C nhưng điều này khá tốt như nó có.
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
Đầu ra:
1 - a
2 - b
3 - c
4 - d
to_enum
cung cấp cho bạn một tor enumera , trong khi enum
theo nghĩa C # / Java là một enumera tion
Đôi khi tất cả những gì tôi cần là có thể lấy giá trị của enum và xác định tên của nó tương tự như thế giới 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'
Điều này với tôi phục vụ mục đích của enum và giữ cho nó rất mở rộng. Bạn có thể thêm nhiều phương thức vào lớp Enum và viola có được chúng miễn phí trong tất cả các enum đã xác định. ví dụ. get_all_names và những thứ như thế.
Một cách tiếp cận khác là sử dụng một lớp Ruby với hàm băm chứa tên và giá trị như được mô tả trong bài đăng trên blog của RubyFleebie sau đây . Điều này cho phép bạn dễ dàng chuyển đổi giữa các giá trị và hằng số (đặc biệt nếu bạn thêm một phương thức lớp để tra cứu tên cho một giá trị nhất định).
Tôi nghĩ rằng cách tốt nhất để thực hiện phép liệt kê như các kiểu là với các ký hiệu vì phần lớn hoạt động như số nguyên (khi nói đến biểu thức, object_id được sử dụng để so sánh); bạn không cần phải lo lắng về việc lập chỉ mục và chúng trông thực sự gọn gàng trong mã của bạn xD
Một cách khác để bắt chước một enum với cách xử lý bình đẳng nhất quán (được chấp nhận một cách không biết xấu hổ từ Dave Thomas). Cho phép các enum mở (giống như các ký hiệu) và các enum đóng (được xác định trước).
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
Hãy thử 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