has_blob_bit_field 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 49981851b7b6ee4e6f47c6dd40d972da94f2214c
4
- data.tar.gz: 42fd5835bb8f988c99e3ec6e79c9d08e9112bccc
3
+ metadata.gz: 84cb16ec03d0b3289a08923dbe26b84f80539290
4
+ data.tar.gz: bb148f70fc3bf032ef79f3b531abf22c32092ab0
5
5
  SHA512:
6
- metadata.gz: 55d8342bd3714c23dcd756a1f075f5eb77ae464e78f4d674b3d02181e1c1cd9712c676a6eb680570cb33967c6647e2bb32518de8a672dae5e24dd0ef4bf90b3d
7
- data.tar.gz: dcb70d44e7418e9f96f289c80a13437fc0c96e75ef4437b6355294b0ec2c698ce617d448502504bb9a87a14c0ac3ecf03e650d97a2746955cb80c82215867c99
6
+ metadata.gz: 7eb4a11088a4a8f9332a169c3c2fafabdd83b7911fbd903edafb1248daaa5b984f93eafa562e2a47ca3b3700bc22d47f88cc6e12f9c72dfb2f5ce50691da9b14
7
+ data.tar.gz: 1906c4b3239b81ead3899813c2eb707d5347342a2c8ca407399ce0635517230f8e568715ecf1d325ddec1fa1d0d9681f48883f5551d267c6db1f803caedce93e
data/README.md CHANGED
@@ -2,22 +2,6 @@
2
2
 
3
3
  A Rails extension to treat a binary column (blob) as a sequence of `true`/`false` flags (bits)
4
4
 
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- ```ruby
10
- gem 'has_blob_bit_field'
11
- ```
12
-
13
- And then execute:
14
-
15
- $ bundle
16
-
17
- Or install it yourself as:
18
-
19
- $ gem install has_blob_bit_field
20
-
21
5
  ## Example Usage
22
6
 
23
7
  ```
@@ -27,33 +11,50 @@ def change
27
11
  end
28
12
 
29
13
  # Model:
30
-
31
14
  class YourModel < ActiveRecord::Base
32
15
  has_blob_field :many_flags
33
16
  end
34
17
 
35
18
  # Usage:
36
-
37
19
  o = YourModel.new
38
20
  o.many_flags.size # => 0
39
21
  o.many_flags.size = 666 # Can be as big as 8 times the binary limit of your column
40
22
  o.many_flags[42] # => false
41
23
  o.many_flags[42] = true
42
24
  o.many_flags[700] # => IndexError
25
+ o.many_flags.count{|f| f} # => 1
26
+ o.many_flags = [false, true, true, false, false, false, false, true]
27
+ o.many_flags.map! {|f| !f}
28
+ o.many_flags == [true, false, false, true, true, true, true, false] # => true
29
+ o.many_flags_blob_changed? # => true
43
30
  ```
44
31
 
45
32
  ## Notes:
46
33
 
47
34
  * The convention is that your column has the same name as your accessing method with a `'_blob'` suffix, but you can specify the column to use when calling `has_blob_field`.
48
35
 
49
- * The first flag is stored using the highest bit of the first byte.
36
+ * The blob is viewed as an array of bits where the first flag is stored using the highest bit of the first byte.
50
37
 
51
38
  * The size is always rounded up to a multiple of 8.
52
39
 
53
- * Accessing out of bounds indices raises an `IndexError`, but code could be adapted easily to return `nil` instead.
54
-
55
40
  * Targets: Ruby 2.0+, Rails 3.0+
56
41
 
42
+ ## Installation
43
+
44
+ Add this line to your application's Gemfile:
45
+
46
+ ```ruby
47
+ gem 'has_blob_bit_field'
48
+ ```
49
+
50
+ And then execute:
51
+
52
+ $ bundle
53
+
54
+ Or install it yourself as:
55
+
56
+ $ gem install has_blob_bit_field
57
+
57
58
  ## Development
58
59
 
59
60
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,5 +1,7 @@
1
1
  module HasBlobBitField
2
2
  class Accessor
3
+ include Enumerable
4
+ include Comparable
3
5
  attr_reader :record, :column
4
6
 
5
7
  def initialize record, column
@@ -7,18 +9,21 @@ module HasBlobBitField
7
9
  @column = column
8
10
  end
9
11
 
10
- def [](index)
11
- val = byte(index)
12
- val & flag(index) != 0
12
+ def [](bit_index)
13
+ bit_index = check_index(bit_index)
14
+ val = byte(bit_index)
15
+ val & flag(bit_index) != 0
13
16
  end
14
17
 
15
- def []=(index, set)
16
- set = !!set # force to true/false
17
- val = byte(index)
18
- mask = flag(index)
19
- if (val & mask != 0) != set
18
+ def []=(bit_index, set)
19
+ bit_index = check_index(bit_index)
20
+ set = coerce_to_bool(set)
21
+ val = byte(bit_index)
22
+ mask = flag(bit_index)
23
+ was_set = val & mask != 0
24
+ if was_set != set
20
25
  notify_of_mutation
21
- raw_value.setbyte(index >> 3, val ^ mask)
26
+ raw_value.setbyte(bit_index >> 3, val ^ mask)
22
27
  end
23
28
  set
24
29
  end
@@ -37,7 +42,7 @@ module HasBlobBitField
37
42
  if s.size > size_in_bytes
38
43
  s[size_in_bytes..s.size] = ''
39
44
  else
40
- s << Array.new(size_in_bytes - s.size, 0).pack('C*')
45
+ s << "\0".b * (size_in_bytes - s.size)
41
46
  end
42
47
  end
43
48
  size_in_bits
@@ -48,28 +53,78 @@ module HasBlobBitField
48
53
 
49
54
  def raw_value(init_nil: false)
50
55
  @record.public_send(@column) ||
51
- (init_nil && notify_of_mutation && @record.public_send(:"#{@column}=", ''.b)) ||
56
+ (init_nil && replace_raw_value(''.b)) ||
52
57
  ''.b
53
58
  end
54
59
 
60
+ def replace(values)
61
+ new_raw_value = if values.class == self.class
62
+ values.raw_value
63
+ else
64
+ masks = (0..7).map {|i| flag(i) }
65
+ self.size = values.size
66
+ values.each_slice(8).map do |flags|
67
+ flags.each_with_index.inject(0) do |sum, (flag, bit_number)|
68
+ sum | (flag ? masks[bit_number] : 0)
69
+ end
70
+ end.pack('C*')
71
+ end
72
+ replace_raw_value new_raw_value
73
+ self
74
+ end
75
+
76
+ def each
77
+ return to_enum unless block_given?
78
+ masks = (0..7).map {|i| flag(i) }
79
+ raw_value.each_byte do |byte|
80
+ masks.each do |mask|
81
+ yield mask & byte != 0
82
+ end
83
+ end
84
+ self
85
+ end
86
+
87
+ def map!
88
+ return to_enum unless block_given?
89
+ replace(map {|b| yield b })
90
+ end
91
+
92
+ def <=>(other)
93
+ value_method = other.class == self.class ? :raw_value : :to_a
94
+ public_send(value_method) <=> other.public_send(value_method)
95
+ end
55
96
 
56
- private
97
+ protected
57
98
  def notify_of_mutation
58
99
  @record.public_send :"#{@column}_will_change!"
59
100
  self
60
101
  end
61
102
 
62
- def flag(index)
63
- 0b1000_0000 >> (index & 0b111)
103
+ def replace_raw_value(raw)
104
+ notify_of_mutation
105
+ @record.public_send(:"#{@column}=", raw)
106
+ end
107
+
108
+ def flag(bit_index)
109
+ 0b1000_0000 >> (bit_index & 0b111)
110
+ end
111
+
112
+ def byte(bit_index)
113
+ raw_value.getbyte(bit_index >> 3) || out_of_bound(bit_index)
114
+ end
115
+
116
+ def coerce_to_bool(value)
117
+ raise TypeError unless value == true || value == false
118
+ value
64
119
  end
65
120
 
66
- def byte(index)
67
- out_of_bound(index) unless index >= 0
68
- raw_value.getbyte(index >> 3) || out_of_bound(index)
121
+ def check_index(bit_index)
122
+ out_of_bound(bit_index) if bit_index < 0
123
+ bit_index
69
124
  end
70
125
 
71
- def out_of_bound(index)
72
- raise IndexError, "#{index} is our of range for #{@column} which has size #{size}. Record: #{@record.inspect}}"
126
+ def out_of_bound(bit_index)
127
+ raise IndexError, "#{bit_index} is our of range for #{@column} which has size #{size}. Record: #{@record.inspect}}"
73
128
  end
74
129
  end
75
130
  end
@@ -8,11 +8,11 @@ module HasBlobBitField
8
8
  def has_blob_bit_field field, column: :"#{field}_blob"
9
9
  class_eval <<-EVAL, __FILE__, __LINE__
10
10
  def #{field}
11
- Accessor.new self, :#{column}
11
+ @_#{field}_accessor ||= Accessor.new self, :#{column}
12
12
  end
13
13
 
14
- def #{field}=
15
- raise "Don't set the pseudo field #{field} directly, use []= on it"
14
+ def #{field}=(values)
15
+ #{field}.replace(values)
16
16
  end
17
17
  EVAL
18
18
  end
@@ -1,3 +1,3 @@
1
1
  module HasBlobBitField
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_blob_bit_field
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-Andre Lafortune
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-20 00:00:00.000000000 Z
11
+ date: 2016-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -138,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
138
  version: '0'
139
139
  requirements: []
140
140
  rubyforge_project:
141
- rubygems_version: 2.2.2
141
+ rubygems_version: 2.5.1
142
142
  signing_key:
143
143
  specification_version: 4
144
144
  summary: Acces a binary column as a sequence of true/false flags