# 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