it-swarm-eu.dev

Enums v Ruby

Jaký je nejlepší způsob, jak realizovat enum idiom v Ruby? Hledám něco, co mohu použít (téměř) jako výčet Java/C #.

290
auramo

Dvě cesty. Symboly (:foo notation) nebo konstanty (FOO notation).

Symboly jsou vhodné, pokud chcete zvýšit čitelnost bez kódu odhazování s doslovnými řetězci.

postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"

Konstanty jsou vhodné, pokud máte základní hodnotu, která je důležitá. Stačí deklarovat modul, aby držel vaše konstanty a pak deklaroval konstanty uvnitř toho.

module Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end

flags = Foo::BAR | Foo::BAZ # flags = 3
289
mlibby

Nejvíce idiomatický způsob, jak toho dosáhnout, je použít symboly. Například místo:

enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

... můžete použít pouze symboly:

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

To je o něco otevřenější než výkřiky, ale dobře se hodí k duchu Ruby.

Symboly také fungují velmi dobře. Například porovnání dvou symbolů pro rovnost je mnohem rychlejší než porovnání dvou řetězců.

52
emk

Jsem překvapen, že nikdo nenabídl něco takového (sklizeného z 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

Které lze použít takto:

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

Příklad:

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

To hraje dobře v databázových scénářích, nebo když se zabýváme konstantami/enumy stylu C (jako je tomu v případě použití FFI , které RAPI široce využívá).

Také se nemusíte starat o překlepy, které způsobují tiché selhání, jako byste použili řešení typu hash.

52
Charles

Používám následující přístup:

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

Líbí se mi tyto výhody:

  1. Seskupuje hodnoty vizuálně jako jeden celek
  2. To dělá nějakou kompilaci-kontrola času (na rozdíl od jen používat symboly)\t
  3. Snadno přistupuji k seznamu všech možných hodnot: jen MY_ENUM
  4. Snadno přistupuji k různým hodnotám: MY_VALUE_1
  5. Může mít hodnoty jakéhokoli typu, nejen symbol

Symboly mohou být lepší, protože nemusíte psát název vnější třídy, pokud je používáte v jiné třídě (MyClass::MY_VALUE_1)

30
Alexey

Pokud používáte Rails 4.2 nebo vyšší, můžete použít Rails enums.

Rails má nyní enums ve výchozím nastavení bez nutnosti zahrnout všechny drahokamy.

To je velmi podobné (a více s funkcemi) Java, C++ enums.

Citován z 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

To je můj přístup k enums v Ruby. Šel jsem na krátkou a sladkou, ne nutně nejvíc C. Nějaké nápady?

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

Vím, že je to dlouho, co ten chlapík tuto otázku poslal, ale měl jsem stejnou otázku a tento příspěvek mi nedal odpověď. Chtěl jsem snadný způsob, jak zjistit, jaké číslo představuje, snadné porovnání a většinu podpory ActiveRecord pro vyhledávání pomocí sloupce představujícího enum.

Nic jsem nenašel, tak jsem udělal úžasnou implementaci nazvanou yinum která umožnila všechno, co jsem hledal. Vyrobil tunu specifikace, takže jsem si jistý, že je to bezpečné.

Některé příklady funkcí:

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

Podívejte se na klenot 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.

Pokud se obáváte překlepů se symboly, ujistěte se, že váš kód vyvolává výjimku, když přistupujete k hodnotě s neexistujícím klíčem. Můžete to provést pomocí fetch spíše než []:

my_value = my_hash.fetch(:key)

nebo tím, že hash vyvolá výjimku ve výchozím nastavení, pokud zadáte neexistující klíč:

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

Pokud hash již existuje, můžete přidat na výjimku chování:

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

Normálně se nemusíte starat o bezpečnost překlepů s konstantami. Pokud chybně napsáte konstantní název, obvykle to vyvolá výjimku.

5
Andrew Grimm

Někdo šel dopředu a napsal ruby ​​klenot zvaný Renum . To prohlašuje, že dostane nejbližší Java/C # jako chování. Osobně jsem se stále učil Ruby a byl jsem trochu šokován, když jsem chtěl, aby konkrétní třída obsahovala statický výčet, možná hash, že to nebylo úplně snadné najít přes google.

4
dlamblin

Snad nejlepší by byl lehký přístup

module MyConstants
  ABC = Class.new
  DEF = Class.new
  GHI = Class.new
end

Tímto způsobem mají hodnoty přidružené názvy, jako v jazyce Java/C #:

MyConstants::ABC
=> MyConstants::ABC

Chcete-li získat všechny hodnoty, můžete to udělat

MyConstants.constants
=> [:ABC, :DEF, :GHI] 

Pokud chcete pořadovou hodnotu výčtu, můžete to udělat

MyConstants.constants.index :GHI
=> 2
4
Daniel Lubarov

Nedávno jsme vydali gem , který implementuje Enums v Ruby. V mém { post najdete odpovědi na vaše otázky. Také jsem zde popsal, proč je naše implementace lepší než ta stávající (ve skutečnosti existuje mnoho implementací této funkce v Ruby ještě jako drahokamy). 

3
ka8725

To se zdá trochu zbytečné, ale je to metodika, kterou jsem použil několikrát, zejména tam, kde se integruji s xml nebo s některými.

#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

To mi dává přísnost c # enum a je vázán na model.

2
jjk

Symboly jsou cesta Ruby. Někdy je však třeba mluvit s nějakým kódem C nebo s něčím nebo s Java, které vystavují určitý výčet různých věcí.


#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

To může být takto použito


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

To může být samozřejmě provedeno abstraktně a můžete hodit vlastní třídu Enum 

2
Jonke

Takhle jsem implementoval enums 

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

pak jeho snadné operace 

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values
2
Masuschi

Vše záleží na tom, jak používáte Java nebo C # enums. Jak budete používat to bude diktovat řešení si vyberete v Ruby.

Vyzkoušejte nativní typ Set, například:

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

Další řešení využívá OpenStruct. Je to docela rovně vpřed a čisté.

https://Ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html

Příklad:

# 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

Většina lidí používá symboly (to je syntaxe :foo_bar). Jsou to jakési jedinečné neprůhledné hodnoty. Symboly nepatří do žádného typu enum-stylu, takže ve skutečnosti nejsou věrným zastoupením typu C enumu, ale je to skoro stejně dobré, jak to jde.

1
Jan Krüger

Někdy vše, co potřebuji, je být schopen přinést hodnotu enum a identifikovat její název podobný světu 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'

To mi slouží účelu enum a udržuje ho velmi rozšiřitelné. Můžete přidat další metody do třídy Enum a viola je zdarma ve všech definovaných výčtech. například. get_all_names a podobné věci.

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

Výstup:

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)

Výstup:

GOOD
1
Hossein

Rychlá a špinavá, cítí se jako 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

Použijte jej jako byste použili Enum:

method_that_needs_options(FeelsLikeAnEnum.Option_1)
0
David Foley

Myslím, že nejlepší způsob, jak implementovat výčet, jako jsou typy, je se symboly, protože do značné míry se chovají jako integer (pokud jde o performace, object_id se používá k porovnání); nemusíte se starat o indexování a ve vašem kódu xD vypadají opravdu elegantně

0
goreorto

Dalším přístupem je použití třídy Ruby s hašem obsahujícím jména a hodnoty, jak je popsáno v následujícím příspěvku blogu RubyFleebie . To vám umožňuje snadno převádět hodnoty a konstanty (zejména pokud přidáte metodu třídy pro vyhledání názvu dané hodnoty).

0
Philippe Monnet

Další způsob, jak napodobit výčet s konzistentním řízením rovnosti (bez ostychu přijatý od Davea Thomase). Umožňuje otevřené enums (podobně jako symboly) a uzavřené (předdefinované) enums.

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

Vyzkoušejte 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

zobrazit více https://github.com/alfa-jpn/inum#usage

0
horun