module Bitfields::ClassMethods

Public Instance Methods

bitfield(column, *args) click to toggle source
# File lib/bitfields.rb, line 42
def bitfield(column, *args)
  column = column.to_sym
  options = extract_bitfield_options args
  bitfield_args << [column, options.dup]

  store_bitfield_values column, options
  add_bitfield_methods column, options
end
bitfield_bits(values) click to toggle source
# File lib/bitfields.rb, line 51
def bitfield_bits(values)
  bits = bitfields.values.reduce({}, :merge)
  values.sum { |bit, on| on ? bits.fetch(bit) : 0 }
end
bitfield_column(bit_name) click to toggle source
# File lib/bitfields.rb, line 56
def bitfield_column(bit_name)
  found = bitfields.detect{|_, bits| bits.keys.include?(bit_name.to_sym) }
  raise "Unknown bitfield #{bit_name}" unless found
  found.first
end
bitfield_sql(bit_values, options={}) click to toggle source
# File lib/bitfields.rb, line 62
def bitfield_sql(bit_values, options={})
  bits = group_bits_by_column(bit_values).sort_by{|c,_| c.to_s }
  bits.map{|column, bit_values| bitfield_sql_by_column(column, bit_values, options) } * ' AND '
end
set_bitfield_sql(bit_values) click to toggle source
# File lib/bitfields.rb, line 67
def set_bitfield_sql(bit_values)
  bits = group_bits_by_column(bit_values).sort_by{|c,_| c.to_s }
  bits.map{|column, bit_values| set_bitfield_sql_by_column(column, bit_values) } * ', '
end

Private Instance Methods

add_bitfield_methods(column, options) click to toggle source
# File lib/bitfields.rb, line 87
def add_bitfield_methods(column, options)
  bitfields[column].keys.each do |bit_name|
    if options[:added_instance_methods] != false
      define_method(bit_name) { bitfield_value(bit_name) }
      define_method("#{bit_name}?") { bitfield_value(bit_name) }
      define_method("#{bit_name}=") { |value| set_bitfield_value(bit_name, value) }

      # Dirty methods usable in before_save contexts
      define_method("#{bit_name}_was") { bitfield_value_was(bit_name) }
      alias_method "#{bit_name}_in_database", "#{bit_name}_was"

      define_method("#{bit_name}_change") { bitfield_value_change(bit_name) }
      alias_method "#{bit_name}_change_to_be_saved", "#{bit_name}_change"

      define_method("#{bit_name}_changed?") { bitfield_value_change(bit_name).present? }
      alias_method "will_save_change_to_#{bit_name}?", "#{bit_name}_changed?"

      define_method("#{bit_name}_became_true?") do
        value = bitfield_value(bit_name)
        value && send("#{bit_name}_was") != value
      end
      define_method("#{bit_name}_became_false?") do
        value = bitfield_value(bit_name)
        !value && send("#{bit_name}_was") != value
      end

      # Dirty methods usable in after_save contexts
      define_method("#{bit_name}_before_last_save") { bitfield_value_before_last_save(bit_name) }
      define_method("saved_change_to_#{bit_name}") { saved_change_to_bitfield_value(bit_name) }
      define_method("saved_change_to_#{bit_name}?") { saved_change_to_bitfield_value(bit_name).present? }
    end

    if options[:scopes] != false
      scope bit_name, bitfield_scope_options(bit_name => true)
      scope "not_#{bit_name}", bitfield_scope_options(bit_name => false)
    end
  end

  include Bitfields::InstanceMethods
end
bit_values_to_on_off(column, bit_values) click to toggle source
# File lib/bitfields.rb, line 172
def bit_values_to_on_off(column, bit_values)
  on = off = 0
  bit_values.each do |bit_name, value|
    bit = bitfields[column][bit_name]
    value ? on += bit : off += bit
  end
  [on, off]
end
bitfield_scope_options(bit_values) click to toggle source
# File lib/bitfields.rb, line 128
def bitfield_scope_options(bit_values)
  -> { where(bitfield_sql(bit_values)) }
end
bitfield_sql_by_column(column, bit_values, options={}) click to toggle source
# File lib/bitfields.rb, line 132
def bitfield_sql_by_column(column, bit_values, options={})
  mode = options[:query_mode] || (bitfield_options[column][:query_mode] || :bit_operator)
  case mode
  when :in_list then
    max = (bitfields[column].values.max * 2) - 1
    bits = (0..max).to_a # all possible bits
    bit_values.each do |bit_name, value|
      bit = bitfields[column][bit_name]
      # reject values with: bit off for true, bit on for false
      bits.reject!{|i| i & bit == (value ? 0 : bit) }
    end
    "#{table_name}.#{column} IN (#{bits * ','})"
  when :bit_operator
    on, off = bit_values_to_on_off(column, bit_values)
    "(#{table_name}.#{column} & #{on+off}) = #{on}"
  when :bit_operator_or
    on, off = bit_values_to_on_off(column, bit_values)
    result = []
    result << "(#{table_name}.#{column} & #{on}) <> 0" if on != 0
    result << "(#{table_name}.#{column} & #{off}) <> #{off}" if off != 0
    result.join(' OR ')
  else raise("bitfields: unknown query mode #{mode.inspect}")
  end
end
extract_bitfield_options(args) click to toggle source
# File lib/bitfields.rb, line 74
def extract_bitfield_options(args)
  options = (args.last.is_a?(Hash) ? args.pop.dup : {})
  args.each_with_index{|field,i| options[2**i] = field } # add fields given in normal args to options
  options
end
group_bits_by_column(bit_values) click to toggle source
# File lib/bitfields.rb, line 162
def group_bits_by_column(bit_values)
  columns = {}
  bit_values.each do |bit_name, value|
    column = bitfield_column(bit_name.to_sym)
    columns[column] ||= {}
    columns[column][bit_name.to_sym] = value
  end
  columns
end
set_bitfield_sql_by_column(column, bit_values) click to toggle source
# File lib/bitfields.rb, line 157
def set_bitfield_sql_by_column(column, bit_values)
  on, off = bit_values_to_on_off(column, bit_values)
  "#{column} = (#{column} | #{on+off}) - #{off}"
end
store_bitfield_values(column, options) click to toggle source
# File lib/bitfields.rb, line 80
def store_bitfield_values(column, options)
  self.bitfields ||= {}
  self.bitfield_options ||= {}
  bitfields[column] = Bitfields.extract_bits(options)
  bitfield_options[column] = options
end