Class: BitMagic::Bits

Inherits:
Object
  • Object
show all
Defined in:
lib/bit_magic/bits.rb

Overview

This is a wrapper class for objects that want bitfield functionality. It implements bit field read, write and attribute field read and updating.

This is usually used alongside Magician (an adapter helper class).

If you’re using this class directly, you can subclass it and set DEFAULT_OPTIONS on your subclass to change default options.

Examples:

Subclass this class to change defaults

# (do not set attribute_name to 'object_id' in real code, it's an example)
class MyBits < BitMagic::Bits
  DEFAULT_OPTIONS = BitMagic::Bits::DEFAULT_OPTIONS.merge({default: 99, :attribute_name => 'object_id'})
end
# and now, if you initialize a new MyBits...
MyBits.new(Object.new).options
# will have default: 99 (instead of 0), and attribute_name: 'object_id'

Constant Summary collapse

BOOLEAN_CASTER =

This casts a given input value into a boolean.

lambda {|i| !(i == false or i == 0) }
DEFAULT_OPTIONS =

Default options

The bool_caster is expected to be overwritten depending on your use-case. eg, form fields can send ‘0’ or ‘f’ to mean false.

{
:attribute_name => 'flags',
:default => 0,
:updater => Proc.new {|bits, new_value| bits.instance.send(:"#{bits.attribute_name}=", new_value) },
:bool_caster => BOOLEAN_CASTER
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(instance, magician_or_field_list = {}, options = {}) ⇒ BitMagic::Bits

This class wraps around any arbitrary objects (instance) that respond to certain methods (a getter and setter for the flag field).

Examples:

Initialize a Bits object

Example = Struct.new('Example', :flags)
# above, Example is a class with the 'flags' instance method
# below, we initialize an instance with flags set to 0
bits = BitMagic::Bits.new(Example.new(0), {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})

Parameters:

  • instance (Object)

    some arbitrary object with bit_magic interest

  • magician_or_field_list (BitMagic::Adapters::Magician, Hash) (defaults to: {})

    either an instance of Magician (usually from an adapter) or a Hash with field name => bit/field bits array as key-pair.

  • options (Hash) (defaults to: {})

    additional options to override defaults

Options Hash (options):

  • :attribute_name (String)

    the name for the attribute, will be used as the getter and setter (by appending ‘=’) on the instance object default: ‘flags’

  • :default (Integer)

    the default value. default: 0

  • :updater (Proc)

    a callable (Proc/lambda/Method) used to update the bit field attribute after it has been changed. default: calls “##attribute_name=(newValue)” on instance

  • :bool_caster (Proc)

    a callable (Proc/lambda/Method) used to cast some input value into a boolean (used with #write) default: cast false or 0 (integer) as false, everything else as true



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/bit_magic/bits.rb', line 68

def initialize(instance, magician_or_field_list = {}, options = {})
  if defined?(BitMagic::Adapters::Magician) and magician_or_field_list.is_a?(BitMagic::Adapters::Magician)
    @magician = magician_or_field_list
    @field_list = @magician.field_list
    @options = @magician.action_options.merge(options)
  else
    @field_list = magician_or_field_list
    @options = self.class::DEFAULT_OPTIONS.merge(options)
  end
  @instance = instance
end

Instance Attribute Details

#field_listHash (readonly)

a hash of field name => field bits key-pairs

Returns:

  • (Hash)

    the current value of field_list



25
26
27
# File 'lib/bit_magic/bits.rb', line 25

def field_list
  @field_list
end

#instanceObject (readonly)

the instance we’re doing operations for

Returns:

  • (Object)

    the current value of instance



25
26
27
# File 'lib/bit_magic/bits.rb', line 25

def instance
  @instance
end

#optionsHash (readonly)

options for this instance

Returns:

  • (Hash)

    the current value of options



25
26
27
# File 'lib/bit_magic/bits.rb', line 25

def options
  @options
end

Instance Method Details

#attribute_nameString

Get the attribute name option (a method on the instance that returns the current bit field value).

In the future, attribute name may be a Proc. This class could also possibly be subclassed and this method overwritten if advanced lookup is necessary.

Returns:

  • (String)

    name of the method we will use to get the bit field value



87
88
89
# File 'lib/bit_magic/bits.rb', line 87

def attribute_name
  @options[:attribute_name]
end

#disabled?(*fields) ⇒ Boolean

Check whether all the given field names or bits are disabled (false or 0) On fields with more than one bit, will return true only if the value of the field is 0 (no bits set).

Examples:

Check fields to see if they are disabled

# The struct is just an example, normally you would define a new class
Example = Struct.new('Example', :flags)
exo = Example.new(0)
bits = Bits.new(exo, {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
# we initialized flags to 0, so everything is disabled
bits.disabled?(:is_odd) #=> true
bits.disabled?(:amount, :is_cool) #=> true
bits.disabled?(10, 5, :is_odd) #=> true

# We now change flags on our instance object
exo.flags = 5 # is_odd = 1, amount = 2, is_cool = 0
bits.disabled?(:is_odd) #=> false
bits.disabled?(:amount, :is_cool) #=> false
bits.disabled?(:amount, :is_odd) #=> false
bits.disabled?(:is_cool) #=> true

Parameters:

  • fields (Symbol, Integer)

    one or more field names or bit indices

Returns:

  • (Boolean)

    true if ALL the given field bits are disabled (all bits are not set or false)



176
177
178
179
180
181
182
183
184
185
186
# File 'lib/bit_magic/bits.rb', line 176

def disabled?(*fields)
  memo = true
  field = self.field
  
  fields.flatten.each do |name|
    break unless memo
    memo &&= (read(name, field) == 0)
  end
  
  memo
end

#enabled?(*fields) ⇒ Boolean

Check whether all the given field name or bits are enabled (true or 1) On fields with more than one bit, will return true if any of the bits are enabled (value > 0)

Examples:

Check fields to see if they are enabled

# The struct is just an example, normally you would define a new class
Example = Struct.new('Example', :flags)
exo = Example.new(0)
bits = Bits.new(exo, {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
# we initialized flags to 0, so nothing is enabled
bits.enabled?(:is_odd) #=> false
bits.enabled?(:amount, :is_cool) #=> false
bits.enabled?(10, 5, :is_odd) #=> false

# We now change flags on our instance object
exo.flags = 5 # is_odd = 1, amount = 2, is_cool = 0
bits.enabled?(:is_odd) #=> true
bits.enabled?(:amount, :is_cool) #=> false
bits.enabled?(:amount, :is_odd) #=> true
bits.enabled?(:is_cool) #=> false

Parameters:

  • fields (Symbol, Integer)

    one or more field names or bit indices

Returns:

  • (Boolean)

    true if ALL the given field bits are enabled



139
140
141
142
143
144
145
146
147
148
149
# File 'lib/bit_magic/bits.rb', line 139

def enabled?(*fields)
  memo = true
  field = self.field
  
  fields.flatten.each do |name|
    break unless memo
    memo &&= (read(name, field) >= 1)
  end
  
  memo
end

#fieldBitMagic::BitField

Get a BitField instance for the current value.

Note: Value changes are NOT tracked and updated into the instance, so call this method directly as needed.

Returns:



194
195
196
# File 'lib/bit_magic/bits.rb', line 194

def field
  BitField.new(self.value)
end

#inspectString

Inspect output.

Returns:

  • (String)

    an #inspect value for this instance



278
279
280
281
282
283
# File 'lib/bit_magic/bits.rb', line 278

def inspect
  short_options = {}
  short_options[:default] = @options[:default]
  short_options[:attribute_name] = @options[:attribute_name]
  "#<#{self.class.to_s} #{@magician ? "bit_magic=#{@magician.bit_magic_name.inspect} " : nil}value=#{self.value}> options=#{short_options.inspect}"
end

#read(name, field = nil) ⇒ Integer Also known as: []

Read a field or bit from its bit index or name

Examples:

Read bit values

# The struct is just an example, normally you would define a new class
Example = Struct.new('Example', :flags)
exo = Example.new(9)
bits = Bits.new(exo, {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
bits.read(:is_odd) #=> 1
bits.read(:amount) #=> 4
bits.read(:is_cool) #=> 0
bits.read(:amount, BitField.new(78)) #=> 7
# Bonus: aliased as []
bits[:is_odd] #=> 1

Parameters:

  • name (Symbol, Integer)

    either the name of the bit (a key in field_list) or a integer bit position

  • field (BitField optional) (defaults to: nil)

    a specific BitField to read from. default: return value of #field

Returns:

  • (Integer)

    a value of the bit (0 or 1) or bits (number from 0 to (2**bit_length) - 1) or nil if the field name is not in the list



219
220
221
222
223
224
225
226
227
# File 'lib/bit_magic/bits.rb', line 219

def read(name, field = nil)
  field ||= self.field
  
  if name.is_a?(Integer)
    field.read_field(name)
  elsif bits = @field_list[name]
    field.read_field(bits)
  end
end

#update(new_value) ⇒ Object

Update the bit field value on the instance with a new value using the updater Proc given during initialization.

Parameters:

  • new_value (Integer)

    the new value for the bit field

Returns:

  • returns the value returned by the updater, usually is the new_value



108
109
110
111
112
# File 'lib/bit_magic/bits.rb', line 108

def update(new_value)
  if @options[:updater].respond_to?(:call)
    @options[:updater].call(self, new_value)
  end
end

#valueInteger

Get the current value from the instance. It should return an integer if the value exists, otherwise anything falsy will be set to the default value given during initialization.

Returns:

  • (Integer)

    the current value for the bit field



96
97
98
99
100
# File 'lib/bit_magic/bits.rb', line 96

def value
  value = @instance.send(attribute_name)
  value ||= @options[:default]
  value
end

#write(name, target_value) ⇒ Object Also known as: []=

Write a field or bit from its field name or index

Note: only the total bits of the field is used from the given value, so any additional bits are ignored. eg: writing a field with one bit as value of 4 will set the bit to 0, writing 5 sets it to 1.

Examples:

Write values to bit fields

# The struct is just an example, normally you would define a new class
Example = Struct.new('Example', :flags)
exo = Example.new(0)
bits = Bits.new(exo, {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
bits.write(:is_odd, 1) #=> 1
bits.write(:amount, 5) #=> 11
exo.flags #=> 11
# Bonus, aliased as :[]=, but note in this mode, the return value is same as given value
bits[:is_cool] = 1 #=> 1
exo.flags #=> 27

Parameters:

  • name (Symbol, Integer, Array<Integer>)

    a field name, or bit position, or array of bit positions

  • target_value (Integer, Array<Integer>)

    the target value for the field (note: technically, this can be anything that responds to :[](index), but usage in that type of context is discouraged without adapter support)

Returns:

  • the return value of the updater Proc, usually is equal to the final master value (with all the bits) after writing this bit



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/bit_magic/bits.rb', line 257

def write(name, target_value)
  if name.is_a?(Symbol)
    self.write(@field_list[name], target_value)
  elsif name.is_a?(Integer)
    self.update self.field.write_bits(name => @options[:bool_caster].call(target_value))
  elsif name.respond_to?(:[]) and target_value.respond_to?(:[])
    bits = {}
    
    name.each_with_index do |bit, i|
      bits[bit] = @options[:bool_caster].call(target_value[i])
    end
    
    self.update self.field.write_bits bits
  end
end