it-swarm-id.com

Enums di Ruby

Apa cara terbaik untuk menerapkan idiom enum di Ruby? Saya mencari sesuatu yang bisa saya gunakan (hampir) seperti Java/C # enums.

290
auramo

Dua arah. Simbol (notasi :foo) atau konstanta (notasi FOO).

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

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.

52
emk

Saya terkejut bahwa tidak ada yang menawarkan sesuatu seperti berikut (dipanen dari 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

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 digunakan RAPI secara ekstensif).

Juga, Anda tidak perlu khawatir tentang kesalahan ketik yang menyebabkan kegagalan diam, seperti yang Anda akan lakukan dengan menggunakan solusi tipe-hash.

52
Charles

Saya menggunakan pendekatan berikut:

class MyClass
  MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end

Saya suka untuk keuntungan berikut:

  1. Ini mengelompokkan nilai-nilai secara visual sebagai satu kesatuan
  2. Ia melakukan pengecekan waktu kompilasi (berbeda dengan hanya menggunakan simbol)
  3. Saya dapat dengan mudah mengakses daftar semua nilai yang mungkin: cukup MY_ENUM
  4. Saya dapat dengan mudah mengakses nilai yang berbeda: MY_VALUE_1
  5. Itu dapat memiliki nilai jenis apa pun, bukan hanya Simbol

Simbol mungkin lebih baik karena Anda tidak perlu menulis nama kelas luar, jika Anda menggunakannya di kelas lain (MyClass::MY_VALUE_1)

30
Alexey

Jika Anda menggunakan Rails 4.2 atau lebih besar, 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
17
vedant

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
7
johnnypez

Saya tahu sudah lama sejak pria 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 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
7
Oded Niv

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

Jika Anda khawatir tentang kesalahan pengetikan 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.

5
Andrew Grimm

Seseorang melanjutkan dan menulis permata Ruby yang disebut 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.

4
dlamblin

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

Baru-baru ini kami merilis gem yang mengimplementasikan Enums di Ruby. Di post saya Anda akan menemukan jawaban atas pertanyaan Anda. Saya juga menjelaskan di sana mengapa implementasi kami lebih baik daripada yang sudah ada (sebenarnya ada banyak implementasi fitur ini di Ruby sebagai permata) 

3
ka8725

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 c # enum dan itu terkait dengan model.

2
jjk

Simbol adalah cara Ruby. 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 menggulung kelas Enum kami sendiri 

2
Jonke

Saya telah mengimplementasikan 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
2
Masuschi

Itu semua tergantung bagaimana Anda menggunakan Java atau C # enums. Cara Anda menggunakannya akan menentukan solusi yang akan Anda pilih di Ruby.

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

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

Kebanyakan orang menggunakan simbol (itulah sintaks :foo_bar). Mereka semacam nilai-nilai buram yang unik. Simbol tidak termasuk jenis enum-style sehingga mereka tidak benar-benar mewakili tipe enum C yang setia, tetapi ini cukup bagus.

1
Jan Krüger

Terkadang yang saya butuhkan adalah untuk dapat 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.

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

Keluaran:

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

Keluaran:

GOOD
1
Hossein

Cepat dan kotor, terasa seperti 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

Gunakan itu seperti Anda akan menggunakan Enum:

method_that_needs_options(FeelsLikeAnEnum.Option_1)
0
David Foley

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

0
goreorto

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

0
Philippe Monnet

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

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

0
horun