module SelectableAttr class Enum include Enumerable class << self def instances @@instances ||= [] end end def initialize(&block) @entries = [] instance_eval(&block) if block_given? SelectableAttr::Enum.instances << self end def entries @entries end def each(&block) entries.each(&block) end def define(id, key, name, options = nil, &block) entry = Entry.new(self, id, key, name, options, &block) entry.instance_variable_set(:@defined_in_code, true) @entries << entry end alias_method :entry, :define def i18n_scope(*path) @i18n_scope = path unless path.empty? @i18n_scope end def match_entry(entry, value, *attrs) attrs.any?{|attr| entry[attr].to_s == value.to_s} end def entry_by(value, *attrs) entries.detect{|entry| match_entry(entry, value, *attrs)} || Entry::NULL end def entry_by_id(id) entry_by(id, :id) end def entry_by_key(key) entry_by(key, :key) end def entry_by_id_or_key(id_or_key) entry_by(id_or_key, :id, :key) end def entry_by_hash(attrs) entries.detect{|entry| attrs.all?{|(attr, value)| entry[attr].to_s == value.to_s }} || Entry::NULL end def [](arg) arg.is_a?(Hash) ? entry_by_hash(arg) : entry_by_id_or_key(arg) end def values(*args) args = args.empty? ? [:name, :id] : args result = entries.collect{|entry| args.collect{|arg| entry.send(arg) }} (args.length == 1) ? result.flatten : result end def map_attrs(attrs, *ids_or_keys) if attrs.is_a?(Array) ids_or_keys.empty? ? entries.map{|entry| attrs.map{|attr|entry.send(attr)}} : ids_or_keys.map do |id_or_key| entry = entry_by_id_or_key(id_or_key) attrs.map{|attr|entry.send(attr)} end else attr = attrs ids_or_keys.empty? ? entries.map(&attr.to_sym) : ids_or_keys.map{|id_or_key|entry_by_id_or_key(id_or_key).send(attr)} end end def ids(*ids_or_keys); map_attrs(:id, *ids_or_keys); end def keys(*ids_or_keys); map_attrs(:key, *ids_or_keys); end def names(*ids_or_keys); map_attrs(:name, *ids_or_keys); end def options(*ids_or_keys); map_attrs([:name, :id], *ids_or_keys); end def key_by_id(id); entry_by_id(id).key; end def id_by_key(key); entry_by_key(key).id; end def name_by_id(id); entry_by_id(id).name; end def name_by_key(key); entry_by_key(key).name; end def find(options = nil, &block) entries.detect{|entry| block_given? ? yield(entry) : entry.match?(options) } || Entry::NULL end def to_hash_array entries.map do |entry| result = entry.to_hash yield(result) if defined? yield result end end def length entries.length end alias_method :size, :length class Entry BASE_ATTRS = [:id, :key, :name] attr_reader :id, :key attr_reader :defined_in_code def initialize(enum, id, key, name, options = nil, &block) @enum = enum @id = id @key = key @name = name @options = options self.instance_eval(&block) if block end attr_reader :name def [](option_key) BASE_ATTRS.include?(option_key) ? send(option_key) : @options ? @options[option_key] : nil end def match?(options) @options === options end def null? false end def null_object? self.null? end def to_hash (@options || {}).merge(:id => @id, :key => @key, :name => name) end NULL = new(nil, nil, nil, nil) do def null?; true; end def name; nil; end end end end end