# frozen-string-literal: true module Sequel module Plugins # The enum plugin allows for easily adding methods to modify the value of # a column. It allows treating the column itself as an enum, returning a # symbol for the related enum value. It also allows for setting up dataset # methods to easily find records having or not having each enum value. # # After loading the plugin, you can call the +enum+ method to define the # methods. The +enum+ method accepts a symbol for the underlying # database column, and a hash with symbol keys for the enum values. # For example, the following call: # # Album.enum :status_id, good: 1, bad: 2 # # Will define the following instance methods: # # Album#good! :: Change +status_id+ to +1+ (does not save the receiver) # Album#bad! :: Change +status_id+ to +2+ (does not save the receiver) # Album#good? :: Return whether +status_id+ is +1+ # Album#bad? :: Return whether +status_id+ is +2+ # # It will override the following instance methods: # # Album#status_id :: Return +:good+/+:bad+ instead of +1+/+2+ (other values returned as-is) # Album#status_id= :: Allow calling with +:good+/+:bad+ to set +status_id+ to +1+/+2+ (other values, # such as 'good'/'bad' set as-is) # # If will define the following dataset methods: # # Album.dataset.good :: Return a dataset filtered to rows where +status_id+ is +1+ # Album.dataset.not_good :: Return a dataset filtered to rows where +status_id+ is not +1+ # Album.dataset.bad:: Return a dataset filtered to rows where +status_id+ is +2+ # Album.dataset.not_bad:: Return a dataset filtered to rows where +status_id+ is not +2+ # # When calling +enum+, you can also provide the following options: # # :prefix :: Use a prefix for methods defined for each enum value. If +true+ is provided at the value, use the column name as the prefix. # For example, with prefix: 'status', the instance methods defined above would be +status_good?+, +status_bad?+, # +status_good!+, and +status_bad!+, and the dataset methods defined would be +status_good+, +status_not_good+, +status_bad+, # and +status_not_bad+. # :suffix :: Use a suffix for methods defined for each enum value. If +true+ is provided at the value, use the column name as the suffix. # For example, with suffix: 'status', the instance methods defined above would be +good_status?+, +bad_status?+, # +good_status!+, and +bad_status!+, and the dataset methods defined would be +good_status+, +not_good_status+, +bad_status+, # and +not_bad_status+. # :override_accessors :: Set to +false+ to not override the column accessor methods. # :dataset_methods :: Set to +false+ to not define dataset methods. # # Note that this does not use a true enum column in the database. If you are # looking for enum support in the database, and your are using PostgreSQL, # Sequel supports that via the pg_enum Database extension. # # Usage: # # # Make all model subclasses handle enums # Sequel::Model.plugin :enum # # # Make the Album class handle enums # Album.plugin :enum module Enum module ClassMethods # Define instance and dataset methods in this class to treat column # as a enum. See Enum documentation for usage. def enum(column, values, opts=OPTS) raise Sequel::Error, "enum column must be a symbol" unless column.is_a?(Symbol) raise Sequel::Error, "enum values must be provided as a hash with symbol keys" unless values.is_a?(Hash) && values.all?{|k,| k.is_a?(Symbol)} if prefix = opts[:prefix] prefix = column if prefix == true prefix = "#{prefix}_" end if suffix = opts[:suffix] suffix = column if suffix == true suffix = "_#{suffix}" end values = Hash[values].freeze inverted = values.invert.freeze unless @enum_methods @enum_methods = Module.new include @enum_methods end @enum_methods.module_eval do unless opts[:override_accessors] == false define_method(column) do v = super() inverted.fetch(v, v) end define_method(:"#{column}=") do |v| super(values.fetch(v, v)) end end values.each do |key, value| define_method(:"#{prefix}#{key}#{suffix}!") do self[column] = value nil end define_method(:"#{prefix}#{key}#{suffix}?") do self[column] == value end end end unless opts[:dataset_methods] == false dataset_module do values.each do |key, value| cond = Sequel[column=>value] where :"#{prefix}#{key}#{suffix}", cond where :"#{prefix}not_#{key}#{suffix}", ~cond end end end end end end end end