module AttrBitwise::ClassMethods

ClassMethods

Public Instance Methods

attr_bitwise(name, column_name: nil, mapping:) click to toggle source

Usage :

attr_bitwise :payment_types, mapping: [:slots, :credits],
  column_name: 'payment_types_value'
# File lib/attr_bitwise.rb, line 63
def attr_bitwise(name, column_name: nil, mapping:)
  column_name = "#{name}_value" unless column_name.present?

  # build mapping
  bitwise_mapping = build_mapping(mapping, name)

  # mask to symbols helper
  define_method("#{name}") { send(:value_getter, column_name, bitwise_mapping) }
  define_method("#{name}=") do |values_or_symbols_array|
    send(:value_setter, column_name, Array(values_or_symbols_array), bitwise_mapping)
  end

  # masks symbol presence
  define_method("#{name.to_s.singularize}?") do |value_or_symbol|
    send(:value?, column_name, force_to_bitwise_value(value_or_symbol, bitwise_mapping))
  end

  # add value to mask
  define_method("add_#{name.to_s.singularize}") do |value_or_symbol|
    send(:add_value, column_name, force_to_bitwise_value(value_or_symbol, bitwise_mapping))
  end

  # remove value from mask
  define_method("remove_#{name.to_s.singularize}") do |value_or_symbol|
    send(:remove_value, column_name, force_to_bitwise_value(value_or_symbol, bitwise_mapping))
  end

  # compute values union against mask
  define_method("#{name}_union") do |*mixed_array|
    self.class.bitwise_union(*mixed_array, name)
  end

  # compute values intersection against mask
  define_method("#{name}_intersection") do |*mixed_array|
    self.class.bitwise_intersection(*mixed_array, name)
  end
end
bitwise_intersection(*mixed_array, name) click to toggle source

given a values_arr ay array, return a possible matches

for a intersection

with PAYMENT_TYPES_MAPPING = { credits: 0b001, slots: 0b010, paypal: 0b100 } see www.calleerlandsson.com/2015/02/16/flags-bitmasks-and-unix-file-system-permissions-in-ruby/

bitwise_intersection(:slots, :credits, ‘payment_types’) => [0b101, 0b100, 0b011, 0b111]

# File lib/attr_bitwise.rb, line 129
def bitwise_intersection(*mixed_array, name)
  values_array = mixed_array.map { |v| to_bitwise_values(v, name) }
  mapping = mapping_from_name(name)
  mask = []
  val = values_array.reduce(&:|)

  mapping.values.each do |pv|
    mask << (pv | val)
  end

  mask.uniq
end
bitwise_union(*mixed_array, name) click to toggle source

given a payment_values array, return a possible matches

for a union

with PAYMENT_TYPES_MAPPING = { credits: 0b001, slots: 0b010, paypal: 0b100 } see www.calleerlandsson.com/2015/02/16/flags-bitmasks-and-unix-file-system-permissions-in-ruby/

bitwise_union(:slots, :credits, ‘payment_types’) => [0b011, 0b111]

# File lib/attr_bitwise.rb, line 108
def bitwise_union(*mixed_array, name)
  values_array = mixed_array.map { |v| to_bitwise_values(v, name) }
  mapping = mapping_from_name(name)
  mask = []

  values_array.each do |pv|
    mapping.values.each do |pvv|
      mask << (pv | pvv)
    end
  end

  mask.uniq
end
force_to_bitwise_value(value_or_symbol, mapping) click to toggle source

Given a raw value (int) or a symbol, return proper raw value (int)

# File lib/attr_bitwise.rb, line 155
def force_to_bitwise_value(value_or_symbol, mapping)
  if value_or_symbol.is_a?(Symbol)
    mapping[value_or_symbol]
  else
    value_or_symbol.to_i
  end
end
to_bitwise_values(object, name) click to toggle source

given an Object, return proper Fixnum value, depending of mapping

# File lib/attr_bitwise.rb, line 143
def to_bitwise_values(object, name)
  mapping = mapping_from_name(name)
  if object.is_a?(Array)
    object.map { |v| force_to_bitwise_value(v, mapping) }
  elsif object.is_a?(Hash)
    object.values.map { |v| force_to_bitwise_value(v, mapping) }
  else
    force_to_bitwise_value(object, mapping)
  end
end

Private Instance Methods

build_mapping(symbols, name) click to toggle source

build internal bitwise key-value mapping

it add a zero value, needed for bits operations

each sym get a power of 2 value

# File lib/attr_bitwise.rb, line 179
def build_mapping(symbols, name)
  mapping = {}.tap do |hash|
    if symbols.is_a?(Hash)
      validate_user_defined_values!(symbols, name)
      hash.merge!(symbols.sort_by{|k,v| v}.to_h)
    else
      symbols.each_with_index do |key, i|
        hash[key] = 2**i
      end
    end
    hash[:empty] = 0
  end
  # put mapping in unique constant
  const_mapping_name = "#{name}_mapping".upcase
  const_set(const_mapping_name, mapping)
end
mapping_from_name(name) click to toggle source

return mapping given a bitwise name

# File lib/attr_bitwise.rb, line 171
def mapping_from_name(name)
  const_get("#{name}_mapping".upcase)
end
validate_user_defined_values!(hash, name) click to toggle source
# File lib/attr_bitwise.rb, line 196
def validate_user_defined_values!(hash, name)
  hash.select{|key,value| (Math.log2(value) % 1.0)!=0}.tap do |invalid_options|
    if invalid_options.any?
      raise(ArgumentError, "#{name} value should be a power of two number (#{invalid_options.to_s})")
    end
  end
end