25 ответов:
двумя способами. Символы (
:fooобозначения) или константы (FOOнотации).символы подходят, когда вы хотите улучшить читаемость без засорения кода литеральными строками.
postal_code[:minnesota] = "MN" postal_code[:new_york] = "NY"константы подходят, когда у вас есть основное значение, которое важно. Просто объявите модуль для хранения ваших констант, а затем объявите константы внутри этого.
module Foo BAR = 1 BAZ = 2 BIZ = 4 end flags = Foo::BAR | Foo::BAZ # flags = 3
самый идиоматичный способ сделать это-использовать символы. Например, вместо:
enum { FOO, BAR, BAZ } myFunc(FOO);...вы можете просто использовать символы:
# 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)Это немного более открытый, чем перечисления, но он хорошо сочетается с рубиновым духом.
символы также работают очень хорошо. Сравнение двух символов для равенства, например, намного быстрее, чем сравнение двух строк.
Я удивлен, что никто не предложил что-то вроде следующего (собранного с Рапи 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который можно использовать так:
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пример:
>> 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Это хорошо играет в сценариях базы данных или при работе с константами/перечислениями стиля C (как в случае с использованием FFI, который Рапи широко использует).
кроме того, вам не нужно беспокоиться о опечатках, вызывающих тихие сбои, так как вы бы с помощью хэш-решение.
Я использую следующий подход:
class MyClass MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2'] endмне это нравится за следующие преимущества:
- он группирует значения визуально как одно целое
- он выполняет некоторую проверку времени компиляции (в отличие от простого использования символов)
- Я могу легко получить доступ к Список всех возможных значений: просто
MY_ENUM- Я могу легко получить доступ к различные значения:
MY_VALUE_1- он может иметь значения любого типа, а не только Символ
символы могут быть лучше, потому что вам не нужно писать имя внешнего класса, если вы используете его в другом классе (
MyClass::MY_VALUE_1)
Если вы используете Rails 4.2 или выше, вы можете использовать перечисления Rails.
Rails теперь имеет перечисления по умолчанию без необходимости включения каких-либо драгоценных камней.
Это очень похоже (и больше с функциями) на перечисления Java, C++.
цитируется из 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
Это мой подход к перечислениям в Ruby. Я шел за коротким и сладким, не обязательно самым C-подобным. Есть мысли?
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
Проверьте ruby-enum gem,https://github.com/dblock/ruby-enum.
class Gender include Enum Gender.define :MALE, "male" Gender.define :FEMALE, "female" end Gender.all Gender::MALE
Я знаю, что прошло много времени с тех пор, как парень опубликовал этот вопрос, но у меня был тот же вопрос, и этот пост не дал мне ответа. Я хотел простой способ увидеть, что представляет собой число, простое сравнение и, прежде всего, поддержку ActiveRecord для поиска с использованием столбца, представляющего перечисление.
Я ничего не нашел, поэтому я сделал удивительную реализацию под названием yinum что позволило все, что я искал. Сделал тонну спецификаций, поэтому я уверен, что это безопасный.
пример характеристики:
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
кто-то пошел дальше и написал рубиновый камень под названием Renum. Он утверждает, что получает самое близкое поведение Java / C#. Лично я все еще изучаю Ruby, и я был немного шокирован, когда хотел, чтобы конкретный класс содержал статическое перечисление, возможно, хэш, что его не совсем легко найти через google.
если вас беспокоят опечатки с символами, убедитесь, что ваш код вызывает исключение при доступе к значению с несуществующим ключом. Вы можете сделать это с помощью
fetch, а не[]:my_value = my_hash.fetch(:key)или путем создания хэша вызвать исключение по умолчанию, если вы предоставляете несуществующий ключ:
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если хэш уже существует, вы можете добавить поведение, вызывающее исключение:
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нормально, вы не должны потревожиться о безопасности опечатки с константы. Если вы неправильно пишете постоянное имя, это обычно вызывает исключение.
возможно, лучший легкий подход будет
module MyConstants ABC = Class.new DEF = Class.new GHI = Class.new endтаким образом значения имеют связанные имена, как в Java / C#:
MyConstants::ABC => MyConstants::ABCчтобы получить все значения, вы можете сделать
MyConstants.constants => [:ABC, :DEF, :GHI]если вы хотите порядковое значение перечисления, вы можете сделать
MyConstants.constants.index :GHI => 2
все зависит от того, как вы используете перечисления Java или C#. Как вы его используете, будет диктовать решение, которое вы выберете в Ruby.
попробуйте уроженца
Setтип, например:>> 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"}>
символы-это рубиновый путь. Однако иногда нужно поговорить с некоторым кодом C или чем-то или Java, которые предоставляют некоторые перечисления для различных вещей.
#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
Это может быть использовано следующим образом
require 'server_roles' sSymb, sEnum =EnumLike.server_role() foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Это, конечно, можно сделать абстрактным, и вы можете свернуть наш собственный класс перечисления
я реализовал перечисления, как это
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тогда его легко сделать операции
EnumType.ENUM_1.label...
enum = EnumType.find_by_id 1...
valueArray = EnumType.values
это кажется немного излишним, но это методология, которую я использовал несколько раз, особенно там, где я интегрирую с xml или что-то подобное.
#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Это дает мне строгость перечисления c#, и он привязан к модели.
другое решение-это использование OpenStruct. Его довольно прямо вперед и очистить.
https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html
пример:
# 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'
большинство людей используют символы (это
:foo_barсинтаксис). Это своего рода уникальные непрозрачные значения. Символы не принадлежат к какому-либо типу enum-стиля, поэтому они на самом деле не являются точным представлением типа enum 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выход:
1-a
2-b
3-c
4-d
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)выход:
GOOD
иногда все, что мне нужно, это иметь возможность получить значение enum и определить его имя, похожее на java world.
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'Это для меня служит цели перечисления и держит его очень расширяемым тоже. Вы можете добавить больше методов в класс Enum, и viola получит их бесплатно во всех определенных перечислениях. например. get_all_names и все такое.
другой подход заключается в использовании класса Ruby с хэшем, содержащим имена и значения, как описано в следующем RubyFleebie сообщение в блоге. Это позволяет легко конвертировать между значениями и константами (особенно если вы добавляете метод класса для поиска имени для данного значения).
Я думаю, что лучший способ реализовать перечисление подобных типов-это символы, так как они в значительной степени ведут себя как целое число (когда дело доходит до performace, object_id используется для сравнения ); вам не нужно беспокоиться об индексации, и они выглядят очень аккуратно в вашем коде xD
еще один способ имитировать перечисление с последовательной обработкой равенства (бесстыдно принятый от Дэйва Томаса). Позволяет открывать перечисления (так же, как символы) и закрытые (предопределенные) перечисления.
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
попробуйте inum. https://github.com/alfa-jpn/inum
class Color < Inum::Base define :RED define :GREEN define :BLUE endColor::RED Color.parse('blue') # => Color::BLUE Color.parse(2) # => Color::GREENсмотрите подробнее https://github.com/alfa-jpn/inum#usage
Comments