it-swarm-vi.tech

Enums trong Ruby

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 #.

290
auramo

Hai lối. Ký hiệu (ký hiệu :foo) hoặc hằng số (ký hiệu FOO).

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
289
mlibby

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.

52
emk

Tôi ngạc nhiên khi không ai đưa ra một cái gì đó như sau (được thu hoạch từ RAPI gem):

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/kiểu kiểu C (như trường hợp khi sử dụng FFI , mà 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.

52
Charles

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:

  1. Nó nhóm các giá trị trực quan như một toàn thể
  2. Nó thực hiện một số kiểm tra thời gian biên dịch (ngược lại với việc chỉ sử dụng các ký hiệu)
  3. Tôi có thể dễ dàng truy cập danh sách tất cả các giá trị có thể: chỉ cần MY_ENUM
  4. Tôi có thể dễ dàng truy cập các giá trị riêng biệt: MY_VALUE_1
  5. Nó có thể có các giá trị thuộc bất kỳ loại nào, không chỉ là Biểu tượng

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)

30
Alexey

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à nhiều tính năng hơn) với các enum Java, C++.

Đượ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
17
vedant

Đâ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 là 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
7
johnnypez

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 những gì số đại diện, so sánh dễ dàng và hầu hết tất cả cá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 được 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
7
Oded Niv

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
7
dB.

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 thay vì []:

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ệ.

5
Andrew Grimm

Ai đó đã đi trước và viết một viên ngọc Ruby có tên 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, rằng nó không dễ tìm thấy qua google.

4
dlamblin

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á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
4
Daniel Lubarov

Gần đây, chúng tôi đã phát hành gem thực hiện Enums trong Ruby. Trong bài đăng 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ý). 

3
ka8725

Đ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 một c # enum và nó được gắn với mô hình.

2
jjk

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 

2
Jonke

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
2
Masuschi

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.

Ví dụ, hãy thử loại Set gốc:

>> 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"}>
2
mislav

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'
2
Roger

Hầu hết mọi người sử dụng các ký hiệu (đó là cú pháp :foo_bar). 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ó.

1
Jan Krüger

Đô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ế.

1
dark_src
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 - một
2 - b
3 - c
4 - d

1
Anu
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)

Đầu ra:

GOOD
1
Hossein

Nhanh và bẩn, cảm giác như 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

Sử dụng nó như bạn sẽ sử dụng Enum:

method_that_needs_options(FeelsLikeAnEnum.Option_1)
0
David Foley

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ì khá giố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

0
goreorto

Một cách tiếp cận khác là sử dụng lớp Ruby với hàm băm chứa tên và giá trị như được mô tả trong phần sau bài đăng trên blog của RubyFleebie . Đ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).

0
Philippe Monnet

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
0
Daniel Doubleday

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

xem thêm https://github.com/alfa-jpn/inum#usage

0
horun