module SimpleSet::ClassMethods

Public Instance Methods

as_set(set_cd, values, options = {}) click to toggle source

Provides ability to create simple sets based on hashes or arrays, backed by integer columns (but not limited to integer columns).

Columns are supposed to be suffixed by _cd, if not, use :column => 'the_column_name', so some example migrations:

add_column :users, :roles_cd, :integer
add_column :users, :permissions, :integer # and a custom column...

and then in your model:

class User
  as_set :roles, [:management, :accounting]
end

# or use a hash:

class User
  as_set :user_permissions, { create_invoice: 1, send_invoice: 2, create_user: 4, all: 7 }, column: 'permissions'
end

Now it’s possible to access the set and the internally stored value like:

john_doe = User.new
john_doe.roles                         #=> []
john_doe.roles = [:accounting]
john_doe.roles                         #=> [:accounting]
john_doe.roles_cd                      #=> 2

And to make life a tad easier: a few shortcut methods to work with the set are also created.

john_doe.accounting?                  #=> true
john_doe.accounting = false
john_doe.accounting?                  #=> false

Configuration options:

  • :column - Specifies a custom column name, instead of the default suffixed _cd column.

  • :prefix - Define a prefix, which is prefixed to the shortcut methods (e.g. <symbol>= and <symbol>?), if it’s set to true the enumeration name is used as a prefix, else a custom prefix (symbol or string) (default is nil => no prefix)

  • :slim - If set to true no shortcut methods for all enumeration values are being generated, if set to :class only class-level shortcut methods are disabled (default is nil => they are generated)

  • :whiny - Boolean value which if set to true will throw an ArgumentError if an invalid value is passed to the setter (e.g. a value for which no enumeration exists). if set to false no exception is thrown and the internal value is left untouched (default is true)

# File lib/simple_set.rb, line 75
def as_set(set_cd, values, options = {})
  options = SimpleSet.default_options.merge({column: "#{set_cd}_cd"}).merge(options)
  options.assert_valid_keys(:column, :prefix, :slim, :whiny)

  metaclass = (class << self; self; end)

  values = SimpleSet::SetHash.new(values)

  define_method("#{set_cd}") do
    current = send(options[:column])
    return nil if current.nil?
    values.select { |k,v| v == current & v }.keys
  end

  define_method("#{set_cd}=") do |new_values|
    real = nil
    if ! new_values.nil? then
      new_values = new_values.reject { |x| x == ''}.collect { |x| x.to_sym }
      real = new_values.collect do |k|
        if values.has_key?(k) then
          values[k]
        else
          raise(ArgumentError, "Invalid set value : #{k}") if options[:whiny]
          0
        end
      end.inject(:|)
    end
    send("#{options[:column]}=", real)
  end

  metaclass.send :define_method, "#{set_cd}" do
    return values.keys
  end

  if options[:slim] != true then
    prefix = options[:prefix] && "#{options[:prefix] == true ? set_cd.to_s.singularize : options[:prefix]}_"
    values.each do |k,code|
      sym = k.to_sym

      define_method("#{prefix}#{sym}?") do
        current = send(options[:column]) || 0
        code == (code & current)
      end

      define_method("#{prefix}#{sym}=") do |value|
        current = send(options[:column]) || 0
        if value then
          current |= code
        else
          current &= ~code
        end
        send("#{options[:column]}=", current)
        code == (current & code)
      end

      unless options[:slim] == :class then
        metaclass.send(:define_method, "#{prefix}#{sym}", Proc.new { |*args| args.first ? k : code })
      end
    end
  end
end