bitfields 0.7.0 → 0.14.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
- SHA1:
3
- metadata.gz: d97ce2d0cab3c7fd6b3d21d91b95961ec0f01b2d
4
- data.tar.gz: cd53f0020540f8d34514c6f99aca1fc1bcdcb285
2
+ SHA256:
3
+ metadata.gz: aa0272ff1f618bce120efefe15d9e33a355eda3ec2bf4d79035d59262f904360
4
+ data.tar.gz: 9707b7a856a7b6da7a801d337b85a51c031a52e6f665d84a78ba6d6a399acdde
5
5
  SHA512:
6
- metadata.gz: 938985833d6fac258ea5ecd54b11880781fc3b1047deec846414d9d7e7890bacefb56c227bf1c5b62088d8501282318c1d3ce8f98c3915982754e49599f11999
7
- data.tar.gz: 946f21fa6884985c602c60ee8f535a97b53ca8effe044e812f5bdd02d1eaaf872afb779d68b667d7f82867db76521026eda4fa7b74d687d9ac27ce9d4f9f5876
6
+ metadata.gz: 35d06d5cbeb950fe38c05ab97920dc58774614793de3cfed986c04743d3d8330bb5a42f3ac46f82d3e0bd54ee23535c9f0b1e9956ecd364a06401f27db19921e
7
+ data.tar.gz: 9bb95fd7d86d30d0e60d6c3e1635f4b1f1723dba552f5f9ebda9fa374cffb40725e62b8187133951307a2cb769abe41d9ad8c0626bc62c489b851cc7967112a6
data/Readme.md CHANGED
@@ -4,22 +4,28 @@ e.g. true-false-false = 1, false-true-false = 2, true-false-true = 5 (1,2,4,8,.
4
4
  ```ruby
5
5
  class User < ActiveRecord::Base
6
6
  include Bitfields
7
- bitfield :my_bits, 1 => :seller, 2 => :insane, 4 => :stupid
7
+ bitfield :my_bits, 1 => :seller, 2 => :insane, 4 => :sensible
8
8
  end
9
9
 
10
- user = User.new(foo: true, insane: true)
11
- user.seller == true
12
- user.stupid? == false
13
- user.my_bits == 3
10
+ user = User.new(seller: true, insane: true)
11
+ user.seller # => true
12
+ user.sensible? # => false
13
+ user.my_bits # => 3
14
14
  ```
15
15
 
16
- - records bitfield_changes `user.bitfield_changes == {foo: [false, true]}` (also `foo_was` / `foo_change` / `foo_changed?` / `foo_became_true?`)
17
- - adds scopes `User.foo.stupid.first` (deactivate with `bitfield ..., scopes: false`)
18
- - builds sql `User.bitfield_sql(insane: true, stupid: false) == '(users.my_bits & 3) = 1'`
19
- - builds index-using sql with `bitfield ... , query_mode: :in_list` and `User.bitfield_sql(insane: true, stupid: false) == 'users.my_bits IN (2, 3)'` (2 and 1+2), often slower than :bit_operator sql especially for high number of bits
20
- - builds update sql `User.set_bitfield_sql(insane: true, stupid: false) == 'my_bits = (my_bits | 6) - 4'`
16
+ - records bitfield_changes `user.bitfield_changes # => {"seller" => [false, true], "insane" => [false, true]}` (also `seller_was` / `seller_change` / `seller_changed?` / `seller_became_true?` / `seller_became_false?`)
17
+ - Individual added methods (i.e, `seller_was`, `seller_changed?`, etc..) can be deactivated with `bitfield ..., added_instance_methods: false`
18
+ - **Note**: ActiveRecord 5.2 changes the behavior of `_was` and `_changed?` methods when used in the context of an `after_save` callback.
19
+ - ActiveRecord 5.1 will use the use the values that were _just_ changed.
20
+ - ActiveRecord 5.2, however, will return the current value for `_was` and `false` for `_changed?` since the previous changes have been persisted.
21
+ - adds scopes `User.seller.sensible.first` (deactivate with `bitfield ..., scopes: false`)
22
+ - builds sql `User.bitfield_sql(insane: true, sensible: false) # => '(users.my_bits & 6) = 1'`
23
+ - builds sql with OR condition `User.bitfield_sql({ insane: true, sensible: true }, query_mode: :bit_operator_or) # => '(users.my_bits & 2) = 2 OR (users.bits & 4) = 4'`
24
+ - builds index-using sql with `bitfield ... , query_mode: :in_list` and `User.bitfield_sql(insane: true, sensible: false) # => 'users.my_bits IN (2, 3)'` (2 and 1+2) often slower than :bit_operator sql especially for high number of bits
25
+ - builds update sql `User.set_bitfield_sql(insane: true, sensible: false) == 'my_bits = (my_bits | 6) - 4'`
21
26
  - **faster sql than any other bitfield lib** through combination of multiple bits into a single sql statement
22
- - gives access to bits `User.bitfields[:my_bits][:stupid] == 4`
27
+ - gives access to bits `User.bitfields[:my_bits][:sensible] # => 4`
28
+ - converts hash to bits `User.bitfield_bits(seller: true) # => 1`
23
29
 
24
30
  Install
25
31
  =======
@@ -37,25 +43,68 @@ t.integer :my_bits, default: 0, null: false
37
43
  add_column :users, :my_bits, :integer, default: 0, null: false
38
44
  ```
39
45
 
46
+ Instance Methods
47
+ ================
48
+
49
+ ### Global Bitfield Methods
50
+ | Method Name | Example (`user = User.new(seller: true, insane: true`) | Result |
51
+ |--------------------|---------------------------------------------------------|-------------------------------------------------------------|
52
+ | `bitfield_values` | `user.bitfield_values` | `{"seller" => true, "insane" => true, "sensible" => false}` |
53
+ | `bitfield_changes` | `user.bitfield_changes` | `{"seller" => [false, true], "insane" => [false, true]}` |
54
+
55
+ ### Individual Bit Methods
56
+ #### Model Getters / Setters
57
+ | Method Name | Example (`user = User.new`) | Result |
58
+ |----------------|-----------------------------|---------|
59
+ | `#{bit_name}` | `user.seller` | `false` |
60
+ | `#{bit_name}=` | `user.seller = true` | `true` |
61
+ | `#{bit_name}?` | `user.seller?` | `true` |
62
+
63
+ #### Dirty Methods:
64
+
65
+ Some, not all, [`ActiveRecord::AttributeMethods::Dirty`](https://api.rubyonrails.org/v5.1.7/classes/ActiveRecord/AttributeMethods/Dirty.html) and [`ActiveModel::Dirty`](https://api.rubyonrails.org/v5.1.7/classes/ActiveModel/Dirty.html) methods can be used on each bitfield:
66
+
67
+ ##### Before Model Persistence
68
+ | Method Name | Example (`user = User.new`) | Result |
69
+ |------------------------------------|------------------------------------|-----------------|
70
+ | `#{bit_name}_was` | `user.seller_was` | `false` |
71
+ | `#{bit_name}_in_database` | `user.seller_in_database` | `false` |
72
+ | `#{bit_name}_change` | `user.seller_change` | `[false, true]` |
73
+ | `#{bit_name}_change_to_be_saved` | `user.seller_change_to_be_saved` | `[false, true]` |
74
+ | `#{bit_name}_changed?` | `user.seller_changed?` | `true` |
75
+ | `will_save_change_to_#{bit_name}?` | `user.will_save_change_to_seller?` | `true` |
76
+ | `#{bit_name}_became_true?` | `user.seller_became_true?` | `true` |
77
+ | `#{bit_name}_became_false?` | `user.seller_became_false?` | `false` |
78
+
79
+
80
+ ##### After Model Persistence
81
+ | Method Name | Example (`user = User.create(seller: true)`) | Result |
82
+ |--------------------------------|---------------------------------------------------|-----------------|
83
+ | `#{bit_name}_before_last_save` | `user.seller_before_last_save` | `false` |
84
+ | `saved_change_to_#{bit_name}` | `user.saved_change_to_seller` | `[false, true]` |
85
+ | `saved_change_to_#{bit_name}?` | `user.saved_change_to_seller?` | `true` |
86
+
87
+ - **Note**: These methods are dynamically defined for each bitfield, and function separately from the real `ActiveRecord::AttributeMethods::Dirty`/`ActiveModel::Dirty` methods. As such, generic methods (e.g. `attribute_before_last_save(:attribute)`) will not work.
88
+
40
89
  Examples
41
90
  ========
42
91
  Update all users
43
92
 
44
93
  ```ruby
45
- User.seller.not_stupid.update_all(User.set_bitfield_sql(seller: true, insane: true))
94
+ User.seller.not_sensible.update_all(User.set_bitfield_sql(seller: true, insane: true))
46
95
  ```
47
96
 
48
97
  Delete the shop when a user is no longer a seller
49
98
 
50
99
  ```ruby
51
- before_save :delete_shop, if: -> { |u| u.seller_change == [true, false]}
100
+ before_save :delete_shop, if: -> { |u| u.seller_change == [true, false] }
52
101
  ```
53
102
 
54
103
  List fields and their respective values
55
104
 
56
105
  ```ruby
57
106
  user = User.new(insane: true)
58
- user.bitfield_values(:my_bits) == { seller: false, insane: true, stupid: false }
107
+ user.bitfield_values(:my_bits) # => { seller: false, insane: true, sensible: false }
59
108
  ```
60
109
 
61
110
  TIPS
@@ -99,9 +148,16 @@ Authors
99
148
  - [John Wilkinson](https://github.com/jcwilk)
100
149
  - [PeppyHeppy](https://github.com/peppyheppy)
101
150
  - [kmcbride](https://github.com/kmcbride)
151
+ - [Justin Aiken](https://github.com/JustinAiken)
152
+ - [szTheory](https://github.com/szTheory)
153
+ - [Reed G. Law](https://github.com/reedlaw)
154
+ - [Rael Gugelmin Cunha](https://github.com/reedlaw)
155
+ - [Alan Wong](https://github.com/naganowl)
156
+ - [Andrew Bates](https://github.com/a-bates)
157
+ - [Shirish Pampoorickal](https://github.com/shirish-pampoorickal)
158
+ - [Sergey Kojin](https://github.com/skojin)
102
159
 
103
160
  [Michael Grosser](http://grosser.it)<br/>
104
161
  michael@grosser.it<br/>
105
162
  License: MIT<br/>
106
163
  [![Build Status](https://travis-ci.org/grosser/bitfields.png)](https://travis-ci.org/grosser/bitfields)
107
-
@@ -1,3 +1,3 @@
1
1
  module Bitfields
2
- Version = VERSION = "0.7.0"
2
+ Version = VERSION = "0.14.0"
3
3
  end
data/lib/bitfields.rb CHANGED
@@ -48,6 +48,11 @@ module Bitfields
48
48
  add_bitfield_methods column, options
49
49
  end
50
50
 
51
+ def bitfield_bits(values)
52
+ bits = bitfields.values.reduce({}, :merge)
53
+ values.sum { |bit, on| on ? bits.fetch(bit) : 0 }
54
+ end
55
+
51
56
  def bitfield_column(bit_name)
52
57
  found = bitfields.detect{|_, bits| bits.keys.include?(bit_name.to_sym) }
53
58
  raise "Unknown bitfield #{bit_name}" unless found
@@ -81,18 +86,34 @@ module Bitfields
81
86
 
82
87
  def add_bitfield_methods(column, options)
83
88
  bitfields[column].keys.each do |bit_name|
84
- define_method(bit_name) { bitfield_value(bit_name) }
85
- define_method("#{bit_name}?") { bitfield_value(bit_name) }
86
- define_method("#{bit_name}=") { |value| set_bitfield_value(bit_name, value) }
87
- define_method("#{bit_name}_was") { bitfield_value_was(bit_name) }
88
- define_method("#{bit_name}_changed?") { bitfield_value_was(bit_name) != bitfield_value(bit_name) }
89
- define_method("#{bit_name}_change") do
90
- values = [bitfield_value_was(bit_name), bitfield_value(bit_name)]
91
- values unless values[0] == values[1]
92
- end
93
- define_method("#{bit_name}_became_true?") do
94
- value = bitfield_value(bit_name)
95
- value && bitfield_value_was(bit_name) != value
89
+ if options[:added_instance_methods] != false
90
+ define_method(bit_name) { bitfield_value(bit_name) }
91
+ define_method("#{bit_name}?") { bitfield_value(bit_name) }
92
+ define_method("#{bit_name}=") { |value| set_bitfield_value(bit_name, value) }
93
+
94
+ # Dirty methods usable in before_save contexts
95
+ define_method("#{bit_name}_was") { bitfield_value_was(bit_name) }
96
+ alias_method "#{bit_name}_in_database", "#{bit_name}_was"
97
+
98
+ define_method("#{bit_name}_change") { bitfield_value_change(bit_name) }
99
+ alias_method "#{bit_name}_change_to_be_saved", "#{bit_name}_change"
100
+
101
+ define_method("#{bit_name}_changed?") { bitfield_value_change(bit_name).present? }
102
+ alias_method "will_save_change_to_#{bit_name}?", "#{bit_name}_changed?"
103
+
104
+ define_method("#{bit_name}_became_true?") do
105
+ value = bitfield_value(bit_name)
106
+ value && send("#{bit_name}_was") != value
107
+ end
108
+ define_method("#{bit_name}_became_false?") do
109
+ value = bitfield_value(bit_name)
110
+ !value && send("#{bit_name}_was") != value
111
+ end
112
+
113
+ # Dirty methods usable in after_save contexts
114
+ define_method("#{bit_name}_before_last_save") { bitfield_value_before_last_save(bit_name) }
115
+ define_method("saved_change_to_#{bit_name}") { saved_change_to_bitfield_value(bit_name) }
116
+ define_method("saved_change_to_#{bit_name}?") { saved_change_to_bitfield_value(bit_name).present? }
96
117
  end
97
118
 
98
119
  if options[:scopes] != false
@@ -123,6 +144,12 @@ module Bitfields
123
144
  when :bit_operator
124
145
  on, off = bit_values_to_on_off(column, bit_values)
125
146
  "(#{table_name}.#{column} & #{on+off}) = #{on}"
147
+ when :bit_operator_or
148
+ on, off = bit_values_to_on_off(column, bit_values)
149
+ result = []
150
+ result << "(#{table_name}.#{column} & #{on}) <> 0" if on != 0
151
+ result << "(#{table_name}.#{column} & #{off}) <> #{off}" if off != 0
152
+ result.join(' OR ')
126
153
  else raise("bitfields: unknown query mode #{mode.inspect}")
127
154
  end
128
155
  end
@@ -176,6 +203,25 @@ module Bitfields
176
203
  send("#{column}_was") & bit != 0
177
204
  end
178
205
 
206
+ def bitfield_value_before_last_save(bit_name)
207
+ column, bit, _ = bitfield_info(bit_name)
208
+ column_before_last_save = send("#{column}_before_last_save")
209
+ column_before_last_save.nil? ? nil : column_before_last_save & bit != 0
210
+ end
211
+
212
+ def bitfield_value_change(bit_name)
213
+ values = [bitfield_value_was(bit_name), bitfield_value(bit_name)]
214
+ values unless values[0] == values[1]
215
+ end
216
+
217
+ def saved_change_to_bitfield_value(bit_name)
218
+ value_before_last_save = bitfield_value_before_last_save(bit_name)
219
+ current_value = bitfield_value(bit_name)
220
+ unless value_before_last_save.nil? || (value_before_last_save == current_value)
221
+ [value_before_last_save, current_value]
222
+ end
223
+ end
224
+
179
225
  def set_bitfield_value(bit_name, value)
180
226
  column, bit, current_value = bitfield_info(bit_name)
181
227
  new_value = TRUE_VALUES.include?(value)
metadata CHANGED
@@ -1,31 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitfields
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-25 00:00:00.000000000 Z
11
+ date: 2020-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: wwtd
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
19
+ version: '5.1'
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '5.1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: activerecord
28
+ name: wwtd
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -116,15 +116,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
116
  requirements:
117
117
  - - ">="
118
118
  - !ruby/object:Gem::Version
119
- version: 1.9.3
119
+ version: 2.2.0
120
120
  required_rubygems_version: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  requirements: []
126
- rubyforge_project:
127
- rubygems_version: 2.4.5.1
126
+ rubygems_version: 3.0.3
128
127
  signing_key:
129
128
  specification_version: 4
130
129
  summary: Save migrations and columns by storing multiple booleans in a single integer