lib/simple_enum.rb in simple_enum-1.3.2 vs lib/simple_enum.rb in simple_enum-1.4.0

- old
+ new

@@ -7,26 +7,22 @@ # Licence:: MIT-Licence (http://www.opensource.org/licenses/mit-license.php) # # See the +as_enum+ documentation for more details. # because we depend on AR and i18n -require 'active_record' require 'i18n' require 'simple_enum/enum_hash' require 'simple_enum/object_support' require 'simple_enum/validation' # Base module which gets included in <tt>ActiveRecord::Base</tt>. See documentation # of +SimpleEnum::ClassMethods+ for more details. module SimpleEnum - # +SimpleEnum+ version string. - VERSION = "1.3.2".freeze - class << self - + # Provides configurability to SimpleEnum, allows to override some defaults which are # defined for all uses of +as_enum+. Most options from +as_enum+ are available, such as: # * <tt>:prefix</tt> - Define a prefix, which is prefixed to the shortcut methods (e.g. <tt><symbol>!</tt> and # <tt><symbol>?</tt>), if it's set to <tt>true</tt> the enumeration name is used as a prefix, else a custom # prefix (symbol or string) (default is <tt>nil</tt> => no prefix) @@ -39,28 +35,31 @@ # <tt>false</tt> no exception is thrown and the internal value is set to <tt>nil</tt> (default is <tt>true</tt>) def default_options @default_options ||= { :whiny => true, :upcase => false - } + } end - + def included(base) #:nodoc: + base.send :class_attribute, :enum_definitions, :instance_write => false, :instance_reader => false + base.enum_definitions = {} base.send :extend, ClassMethods end end - + module ClassMethods + # Provides ability to create simple enumerations based on hashes or arrays, backed # by integer columns (but not limited to integer columns). # # Columns are supposed to be suffixed by <tt>_cd</tt>, if not, use <tt>:column => 'the_column_name'</tt>, # so some example migrations: # # add_column :users, :gender_cd, :integer # add_column :users, :status, :integer # and a custom column... - # + # # and then in your model: # # class User < ActiveRecord::Base # as_enum :gender, [:male, :female] # end @@ -78,11 +77,11 @@ # john_doe.gender = :male # john_doe.gender # => :male # john_doe.gender_cd # => 0 # # And to make life a tad easier: a few shortcut methods to work with the enumeration are also created. - # + # # john_doe.male? # => true # john_doe.female? # => false # john_doe.female! # => :female (set's gender to :female => gender_cd = 1) # john_doe.male? # => false # @@ -125,16 +124,16 @@ # To disable the generation of the shortcut methods for all enumeration values, add <tt>:slim => true</tt> to # the options. # # class Address < ActiveRecord::Base # as_enum :canton, {:aargau => 'ag', ..., :wallis => 'vs', :zug => 'zg', :zurich => 'zh'}, :slim => true - # end + # end # # home = Address.new(:canton => :zurich, :street => 'Bahnhofstrasse 1', ...) # home.canton # => :zurich # home.canton_cd # => 'zh' - # home.aargau! # throws NoMethodError: undefined method `aargau!' + # home.aargau! # throws NoMethodError: undefined method `aargau!' # Address.aargau # throws NoMethodError: undefined method `aargau` # # This is especially useful if there are (too) many enumeration values, or these shortcut methods # are not required. # @@ -153,97 +152,94 @@ def as_enum(enum_cd, values, options = {}) options = SimpleEnum.default_options.merge({ :column => "#{enum_cd}_cd" }).merge(options) options.assert_valid_keys(:column, :whiny, :prefix, :slim, :upcase) metaclass = (class << self; self; end) - + # convert array to hash... values = SimpleEnum::EnumHash.new(values) values_inverted = values.invert - + # store info away - write_inheritable_attribute(:enum_definitions, {}) if enum_definitions.nil? - enum_definitions[enum_cd] = enum_definitions[options[:column]] = { :name => enum_cd, :column => options[:column], :options => options } - - # generate getter + self.enum_definitions = {} if self.enum_definitions.nil? + self.enum_definitions[enum_cd] = self.enum_definitions[options[:column]] = { :name => enum_cd, :column => options[:column], :options => options } + + # generate getter define_method("#{enum_cd}") do id = read_attribute options[:column] values_inverted[id] end - + # generate setter define_method("#{enum_cd}=") do |new_value| v = new_value.blank? ? nil : values[new_value.to_sym] raise(ArgumentError, "Invalid enumeration value: #{new_value}") if (options[:whiny] and v.nil? and !new_value.blank?) write_attribute options[:column], v end - - # allow access to defined values hash, e.g. in a select helper or finder method. + + # allow access to defined values hash, e.g. in a select helper or finder method. attr_name = enum_cd.to_s.pluralize enum_attr = :"#{attr_name.downcase}_enum_hash" - write_inheritable_attribute(enum_attr, values) - + class_eval(<<-RUBY, __FILE__, __LINE__ + 1) + class_attribute #{enum_attr.inspect}, :instance_write => false, :instance_reader => false + def self.#{attr_name}(*args) - return read_inheritable_attribute(#{enum_attr.inspect}) if args.first.nil? - return read_inheritable_attribute(#{enum_attr.inspect})[args.first] if args.size == 1 - args.inject([]) { |ary, sym| ary << read_inheritable_attribute(#{enum_attr.inspect})[sym]; ary } + return #{enum_attr} if args.first.nil? + return #{enum_attr}[args.first] if args.size == 1 + args.inject([]) { |ary, sym| ary << #{enum_attr}[sym]; ary } end - + def self.#{attr_name}_for_select(attr = :key, &block) self.#{attr_name}.map do |k,v| [block_given? ? yield(k,v) : self.human_enum_name(#{attr_name.inspect}, k), attr == :value ? v : k] end end RUBY - + + # write values + self.send "#{enum_attr}=", values + # only create if :slim is not defined if options[:slim] != true # create both, boolean operations and *bang* operations for each # enum "value" prefix = options[:prefix] && "#{options[:prefix] == true ? enum_cd : options[:prefix]}_" - + values.each do |k,code| sym = k.to_enum_sym - + define_method("#{prefix}#{sym}?") do code == read_attribute(options[:column]) end define_method("#{prefix}#{sym}!") do write_attribute options[:column], code sym end - + # allow class access to each value unless options[:slim] === :class metaclass.send(:define_method, "#{prefix}#{sym}", Proc.new { |*args| args.first ? k : code }) end end end end include Validation - + def human_enum_name(enum, key, options = {}) klasses = self.respond_to?(:descendants) ? descendants : ancestors defaults = ([self] + klasses).map { |klass| :"#{klass.name.underscore}.#{enum}.#{key}" } defaults << :"#{enum}.#{key}" defaults << options.delete(:default) if options[:default] defaults << "#{key}".humanize options[:count] ||= 1 I18n.translate(defaults.shift, options.merge(:default => defaults.flatten, :scope => [:activerecord, :enums])) end - - protected - # Returns enum definitions as defined by each call to - # +as_enum+. - def enum_definitions - read_inheritable_attribute(:enum_definitions) - end end end # Tie stuff together and load translations if ActiveRecord is defined Object.send(:include, SimpleEnum::ObjectSupport) - -ActiveRecord::Base.send(:include, SimpleEnum) + +ActiveRecord::Base.send(:include, SimpleEnum) if defined?(ActiveRecord) I18n.load_path << File.join(File.dirname(__FILE__), '..', 'locales', 'en.yml')