shiftable 0.5.0 → 0.7.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
  SHA256:
3
- metadata.gz: 678fdd1fc65199227c201ecd4346e4c0019c78747d949ac9b4dcba8b38158c24
4
- data.tar.gz: 3f527ed0b2e70a2e677b006320b3e667d0512ccfcacc5bb76232129b9f37c6d0
3
+ metadata.gz: 5307ad9f6d83499df06dcd5b57745dfeed1522f5701708018c61e5b3a3b6bfba
4
+ data.tar.gz: 5452233e37e38c732340e1f7f825da3c020799b2600b5b692087af44969c2c73
5
5
  SHA512:
6
- metadata.gz: c281d086eabfbce50dcd9f3098374656a8c7536b0ce1d5e21d5262c6e205172f90930aa7120a747904d6599313c235adba0bc5487011fa7b5df0e8c90af9157b
7
- data.tar.gz: 217eb1fb303dbc45ccb962bddbff8c4dc246b20351b1f1f2312b9e4ea8c4db7a90360c089b040f0276e544a38193d01feb1728bd6f3949dfe849509a12d38a64
6
+ metadata.gz: d7ed429d81143625afb5717143a877b8de5da2729be8519ed87246353051aa26a8f94f9d33765a9b158b7fc6fa21ed35c2017cadf84e26b669c399d61992f627
7
+ data.tar.gz: ba888b498d8da4a6070f29346426b0ea05a23f959ff208400b5c1067394d5d7301850f99dce3cf0a90b712fd57b6dc397d5b871f7d81723901fd0b60d270329f
data/CHANGELOG.md CHANGED
@@ -12,6 +12,26 @@
12
12
  ### Removed
13
13
 
14
14
 
15
+ ## [0.7.0] - 2021-11-15
16
+ ### Changed
17
+
18
+ - Make the `Shifting` Relation available to the `each` wrapper, as a block parameter
19
+
20
+ ## [0.6.1] - 2021-11-14
21
+ ### Added
22
+
23
+ - Support for using save with bang (`save!`) to raise error when ActiveRecord save fails.
24
+
25
+ ## [0.6.0] - 2021-11-12
26
+ ### Added
27
+
28
+ - Support for wrappers/hooks around each record shift, and around the entire set (see examples in specs or README)
29
+
30
+ ## [0.5.1] - 2021-11-12
31
+ ### Fixed
32
+
33
+ - Documentation typos in README
34
+
15
35
  ## [0.5.0] - 2021-11-12
16
36
  ### Added
17
37
 
@@ -76,4 +96,16 @@
76
96
 
77
97
  [0.3.0]: https://github.com/pboling/shiftable/releases/tag/v0.3.0
78
98
 
79
- [0.4.0]: https://github.com/pboling/shiftable/releases/tag/v0.4.0
99
+ [0.4.0]: https://github.com/pboling/shiftable/releases/tag/v0.4.0
100
+
101
+ [0.4.1]: https://github.com/pboling/shiftable/releases/tag/v0.4.1
102
+
103
+ [0.5.0]: https://github.com/pboling/shiftable/releases/tag/v0.5.0
104
+
105
+ [0.5.1]: https://github.com/pboling/shiftable/releases/tag/v0.5.1
106
+
107
+ [0.6.0]: https://github.com/pboling/shiftable/releases/tag/v0.6.0
108
+
109
+ [0.6.1]: https://github.com/pboling/shiftable/releases/tag/v0.6.1
110
+
111
+ [0.7.0]: https://github.com/pboling/shiftable/releases/tag/v0.7.0
data/README.md CHANGED
@@ -80,7 +80,7 @@ But how can you accomplish this? If you used the `shiftable` gem, won't take but
80
80
  class Spaceship < ActiveRecord::Base
81
81
  belongs_to :captain
82
82
  extend Shiftable::Single.new belongs_to: :captain, has_one: :spaceship, precheck: true,
83
- before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
83
+ before_shift: ->(shifting_rel) { shifting_rel.result.ownership_changes += 1 }
84
84
  end
85
85
  ```
86
86
 
@@ -111,11 +111,10 @@ federation! And in a run-in with their arch-Nemesis the Plinth-inth,
111
111
  all federation spaceships are commandeered! You are ruined!
112
112
 
113
113
  ```ruby
114
-
115
114
  class Spaceship < ActiveRecord::Base
116
115
  belongs_to :space_federation
117
116
  extend Shiftable::Collection.new belongs_to: :space_federation, has_many: :spaceships,
118
- before_shift: lambda { |shifting_rel:, shift_to:, shift_from:|
117
+ before_shift: lambda { |shifting_rel|
119
118
  shifting_rel.each { |spaceship| spaceship.federation_changes += 1 }
120
119
  }
121
120
  end
@@ -153,9 +152,9 @@ class SpaceTreatySignature < ActiveRecord::Base
153
152
  # )
154
153
  extend Shiftable::Collection.new(
155
154
  belongs_to: :signatory, has_many: :space_treaty_signature,
156
- polymorphic_type: "SpaceFederation",
155
+ polymorphic: { type: "SpaceFederation", as: :signatory },
157
156
  method_prefix: "space_federation_",
158
- before_shift: lambda { |shifting_rel:, shift_to:, shift_from:|
157
+ before_shift: lambda { |shifting_rel|
159
158
  # Each item in shifting_rel is an instance of the class where Shiftable::Collection is defined,
160
159
  # in this case: SpaceTreatySignature
161
160
  # And each of them has a signatory which is of type "SpaceFederation",
@@ -171,7 +170,7 @@ class SpaceFederation < ActiveRecord::Base
171
170
  has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
172
171
  has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
173
172
  def assimilate_from(other_federation)
174
- SpaceTreatySignature.space_federation_shift_cx(shift_to: self, shift_from: other_federation)
173
+ SpaceTreatySignature.space_federation_shift_pcx(shift_to: self, shift_from: other_federation)
175
174
  end
176
175
  end
177
176
 
@@ -189,7 +188,51 @@ class SpaceStation < ActiveRecord::Base
189
188
  has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
190
189
  has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
191
190
  end
192
- ```#<--rubocop/md-->#<--rubocop/md-->`
191
+ ```
192
+
193
+ ### Wrapping a shift
194
+
195
+ For example, in a transaction. Let's update the nemesis foundation example from above with a transaction shift_each_wrapper,
196
+ which we'll pull from the [`activerecord-transactionable`](https://github.com/pboling/activerecord-transactionable) gem, which provides best practice framing around transactions.
197
+
198
+ ```ruby
199
+ class Spaceship < ActiveRecord::Base
200
+ belongs_to :space_federation
201
+ extend Shiftable::Collection.new(
202
+ belongs_to: :space_federation,
203
+ has_many: :spaceships,
204
+ before_shift: lambda { |shifting_rel|
205
+ shifting_rel.each { |spaceship| spaceship.federation_changes += 1 }
206
+ },
207
+ wrapper: {
208
+ each: lambda { |rel, record, &block|
209
+ tresult = record.transaction_wrapper(outside_rescued_errors: ActiveRecord::RecordNotUnique) do
210
+ puts "melon #{record.name} honey #{rel.count}"
211
+ block.call # does the actual saving!
212
+ end
213
+ # NOTE: The value returned by the wrapper will also be returned by the call to `shift_cx`.
214
+ # You could return the whole tresult object here, instead of just true/false!
215
+ tresult.success?
216
+ },
217
+ all: lambda { |rel, &block|
218
+ tresult = Spaceship.transaction_wrapper do
219
+ puts "can you eat #{rel.count} shoes"
220
+ block.call
221
+ end
222
+ tresult.success?
223
+ }
224
+ }
225
+ )
226
+ end
227
+
228
+ class SpaceFederation < ActiveRecord::Base
229
+ has_many :spaceships
230
+
231
+ def all_spaceships_commandeered_by(nemesis_federation)
232
+ Spaceship.shift_cx(shift_to: nemesis_federation, shift_from: self)
233
+ end
234
+ end
235
+ ```
193
236
 
194
237
  ### Complete example
195
238
 
@@ -207,13 +250,32 @@ end
207
250
  class Spaceship < ActiveRecord::Base
208
251
  belongs_to :captain
209
252
  extend Shiftable::Single.new belongs_to: :captain, has_one: :spaceship, precheck: true,
210
- before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
253
+ before_shift: ->(shifting_rel) { shifting_rel.result.ownership_changes += 1 }
211
254
 
212
255
  belongs_to :space_federation
213
- extend Shiftable::Collection.new belongs_to: :space_federation, has_many: :spaceships,
214
- before_shift: lambda { |shifting_rel:, shift_to:, shift_from:|
215
- shifting_rel.each { |spaceship| spaceship.federation_changes += 1 }
216
- }
256
+ extend Shiftable::Collection.new(
257
+ belongs_to: :space_federation,
258
+ has_many: :spaceships,
259
+ before_shift: lambda { |shifting_rel|
260
+ shifting_rel.each { |spaceship| spaceship.federation_changes += 1 }
261
+ },
262
+ wrapper: {
263
+ each: lambda { |rel, record, &block|
264
+ tresult = record.transaction_wrapper(outside_rescued_errors: ActiveRecord::RecordNotUnique) do
265
+ puts "melon #{record.name} honey #{rel.count}"
266
+ block.call # does the actual saving!
267
+ end
268
+ tresult.success?
269
+ },
270
+ all: lambda { |rel, &block|
271
+ tresult = Spaceship.transaction_wrapper do
272
+ puts "can you eat #{rel.count} shoes"
273
+ block.call
274
+ end
275
+ tresult.success?
276
+ }
277
+ }
278
+ )
217
279
  end
218
280
 
219
281
  class SpaceFederation < ActiveRecord::Base
@@ -241,7 +303,7 @@ class SpaceTreatySignature < ActiveRecord::Base
241
303
  belongs_to :signatory, polymorphic: true
242
304
  extend Shiftable::Collection.new(
243
305
  belongs_to: :signatory, has_many: :space_treaty_signatures,
244
- polymorphic_type: "SpaceFederation",
306
+ polymorphic: { type: "SpaceFederation", as: :signatory },
245
307
  method_prefix: "space_federation_"
246
308
  )
247
309
  end
@@ -311,7 +373,7 @@ the [Pessimistic Version Constraint][pvc] with two digits of precision.
311
373
  For example:
312
374
 
313
375
  ```ruby
314
- spec.add_dependency "shiftable", "~> 0.4"
376
+ spec.add_dependency "shiftable", "~> 0.7"
315
377
  ```
316
378
 
317
379
  ## Contact
@@ -16,7 +16,7 @@ module Shiftable
16
16
  class Collection < Module
17
17
  # associations: belongs_to, has_many
18
18
  # options: method_prefix, before_shift
19
- def initialize(belongs_to:, has_many:, polymorphic: nil, method_prefix: nil, before_shift: nil)
19
+ def initialize(belongs_to:, has_many:, polymorphic: nil, method_prefix: nil, before_shift: nil, wrapper: nil)
20
20
  # Ruby's Module initializer doesn't take any arguments
21
21
  super()
22
22
 
@@ -39,7 +39,12 @@ module Shiftable
39
39
  method_prefix: method_prefix,
40
40
  # will prevent the save if it returns false
41
41
  # allows for any custom logic to be run, such as setting attributes, prior to the shift (save).
42
- before_shift: before_shift
42
+ before_shift: before_shift,
43
+ # wrapper: {
44
+ # all: ->() { klass.transaction_wrapper { yield } },
45
+ # each: ->() { klass.transaction_wrapper { yield } },
46
+ # }
47
+ wrapper: wrapper
43
48
  },
44
49
  type: polymorphic ? :pcx : :cx
45
50
  )
@@ -68,8 +73,8 @@ module Shiftable
68
73
  define_method(:"#{prefix}shift_#{type}_column") do
69
74
  signature.send("shift_#{type}_column")
70
75
  end
71
- define_method(:"#{prefix}shift_#{type}") do |shift_to:, shift_from:|
72
- signature.shift_data!(shift_to: shift_to, shift_from: shift_from)
76
+ define_method(:"#{prefix}shift_#{type}") do |shift_to:, shift_from:, bang: false|
77
+ signature.shift_data!(shift_to: shift_to, shift_from: shift_from, bang: bang)
73
78
  end
74
79
  end
75
80
  end
@@ -24,6 +24,10 @@ module Shiftable
24
24
  raise ArgumentError, "exactly two distinct associations must be provided" if invalid_number_of_associations?
25
25
  end
26
26
 
27
+ def wrapper
28
+ options[:wrapper] || {}
29
+ end
30
+
27
31
  def polymorphic_type
28
32
  options.dig(:polymorphic, :type)
29
33
  end
@@ -71,16 +75,18 @@ module Shiftable
71
75
  associations[:has_many]
72
76
  end
73
77
 
74
- def shift_data!(shift_to:, shift_from:)
78
+ def shift_data!(shift_to:, shift_from:, bang: false)
75
79
  validate_relationships
76
80
  shifting_rel = ShiftingRelation.new(
77
81
  to: shift_to,
78
82
  from: shift_from,
79
83
  column: shift_column,
80
- base: base
84
+ base: base,
85
+ wrapper: wrapper,
86
+ bang: bang
81
87
  )
82
- shifting_rel.shift do |result|
83
- before_shift&.call(shifting_rel: result, shift_to: shift_to, shift_from: shift_from)
88
+ shifting_rel.shift do
89
+ before_shift&.call(shifting_rel)
84
90
  end
85
91
  end
86
92
  end
@@ -91,7 +97,7 @@ module Shiftable
91
97
  # associations[:has_many]
92
98
  # end
93
99
 
94
- def shift_data!(shift_to:, shift_from:)
100
+ def shift_data!(shift_to:, shift_from:, bang: false)
95
101
  validate_relationships
96
102
  shifting_rel = ShiftingPolymorphicRelation.new(
97
103
  to: shift_to,
@@ -101,10 +107,12 @@ module Shiftable
101
107
  as: polymorphic_as,
102
108
  id_column: shift_pcx_column
103
109
  },
104
- base: base
110
+ base: base,
111
+ wrapper: wrapper,
112
+ bang: bang
105
113
  )
106
- shifting_rel.shift do |result|
107
- before_shift&.call(shifting_rel: result, shift_to: shift_to, shift_from: shift_from)
114
+ shifting_rel.shift do
115
+ before_shift&.call(shifting_rel)
108
116
  end
109
117
  end
110
118
  end
@@ -119,18 +127,20 @@ module Shiftable
119
127
  options[:precheck]
120
128
  end
121
129
 
122
- def shift_data!(shift_to:, shift_from:)
130
+ def shift_data!(shift_to:, shift_from:, bang: false)
123
131
  validate_relationships
124
132
  shifting = ShiftingRecord.new(
125
133
  to: shift_to,
126
134
  from: shift_from,
127
135
  column: shift_column,
128
- base: base
136
+ base: base,
137
+ wrapper: wrapper,
138
+ bang: bang
129
139
  ) do
130
140
  !precheck || !shift_to.send(has_rel)
131
141
  end
132
- shifting.shift do |result|
133
- before_shift&.call(shifting: result, shift_to: shift_to, shift_from: shift_from)
142
+ shifting.shift do
143
+ before_shift&.call(shifting)
134
144
  end
135
145
  end
136
146
  end
@@ -3,9 +3,13 @@
3
3
  module Shiftable
4
4
  # Gets data to be shifted
5
5
  class Shifting
6
- attr_reader :to, :from, :column, :base, :result, :run_save
6
+ # Public API
7
+ attr_accessor :result, :bang, :shift_all_wrapper, :shift_each_wrapper
8
+ attr_reader :to, :from
9
+ # Internal API
10
+ attr_reader :column, :base, :run_save
7
11
 
8
- def initialize(to:, from:, column:, base:)
12
+ def initialize(to:, from:, column:, base:, wrapper:, bang:)
9
13
  @to = to
10
14
  @from = from
11
15
  @column = column
@@ -14,6 +18,9 @@ module Shiftable
14
18
  do_query = block_given? ? yield : true
15
19
  @result = do_query ? query : nil
16
20
  @run_save = true
21
+ @shift_all_wrapper = wrapper[:all]
22
+ @shift_each_wrapper = wrapper[:each]
23
+ @bang = bang
17
24
  end
18
25
 
19
26
  # def found?
@@ -31,6 +38,28 @@ module Shiftable
31
38
  raise ArgumentError, "shift_from must have an id (primary key) value, but is: #{from&.id}" unless from&.id
32
39
  end
33
40
 
41
+ def run_save!
42
+ if shift_all_wrapper
43
+ shift_all_wrapper.call(self) do
44
+ do_saves
45
+ end
46
+ else
47
+ do_saves
48
+ end
49
+ end
50
+
51
+ def do_saves
52
+ if shift_each_wrapper
53
+ each do |rec|
54
+ shift_each_wrapper.call(self, rec) do
55
+ bang ? rec.save! : rec.save
56
+ end
57
+ end
58
+ else
59
+ bang ? each(&:save!) : each(&:save)
60
+ end
61
+ end
62
+
34
63
  # def query
35
64
  # raise "query must be defined in a subclass"
36
65
  # end
@@ -28,8 +28,10 @@ module Shiftable
28
28
  each do |record|
29
29
  record.send("#{polymorphic_id_column}=", to.id)
30
30
  end
31
- @run_save = yield result if block_given?
32
- each(&:save) if run_save
31
+ @run_save = yield if block_given?
32
+ return result unless run_save
33
+
34
+ run_save!
33
35
  result
34
36
  end
35
37
 
@@ -12,12 +12,34 @@ module Shiftable
12
12
  return false unless found?
13
13
 
14
14
  result.send("#{column}=", to.id)
15
- @run_save = yield result if block_given?
16
- result.save if run_save
15
+ @run_save = yield if block_given?
16
+ return nil unless run_save
17
+
18
+ run_save!
17
19
  end
18
20
 
19
21
  private
20
22
 
23
+ def run_save!
24
+ if shift_all_wrapper
25
+ shift_all_wrapper.call(self) do
26
+ do_save
27
+ end
28
+ else
29
+ do_save
30
+ end
31
+ end
32
+
33
+ def do_save
34
+ if shift_each_wrapper
35
+ shift_each_wrapper.call(self, result) do
36
+ bang ? result.save! : result.save
37
+ end
38
+ else
39
+ bang ? result.save! : result.save
40
+ end
41
+ end
42
+
21
43
  def query
22
44
  base.find_by(column => from.id)
23
45
  end
@@ -20,8 +20,10 @@ module Shiftable
20
20
  each do |record|
21
21
  record.send("#{column}=", to.id)
22
22
  end
23
- @run_save = yield result if block_given?
24
- each(&:save) if run_save
23
+ @run_save = yield if block_given?
24
+ return result unless run_save
25
+
26
+ run_save!
25
27
  result
26
28
  end
27
29
 
@@ -14,14 +14,14 @@
14
14
  # belongs_to: :captain,
15
15
  # has_one: :spaceship,
16
16
  # precheck: true,
17
- # before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
17
+ # before_shift: ->(shifting_rel) { shifting_rel.result..ownership_changes += 1 }
18
18
  # )
19
19
  # end
20
20
  #
21
21
  module Shiftable
22
22
  # Inheriting from Module is a powerful pattern. If you like it checkout the debug_logging gem!
23
23
  class Single < Module
24
- def initialize(belongs_to:, has_one:, method_prefix: nil, precheck: true, before_shift: nil)
24
+ def initialize(belongs_to:, has_one:, method_prefix: nil, precheck: true, before_shift: nil, wrapper: nil)
25
25
  # Ruby's Module initializer doesn't take any arguments
26
26
  super()
27
27
 
@@ -44,7 +44,8 @@ module Shiftable
44
44
  method_prefix: method_prefix,
45
45
  # will prevent the save if it returns false
46
46
  # allows for any custom logic to be run, such as setting attributes, prior to the shift (save).
47
- before_shift: before_shift
47
+ before_shift: before_shift,
48
+ wrapper: wrapper
48
49
  },
49
50
  type: :sg
50
51
  )
@@ -72,8 +73,8 @@ module Shiftable
72
73
  define_method(:"#{prefix}shift_column") do
73
74
  signature.send("shift_#{signature.type}_column")
74
75
  end
75
- define_method(:"#{prefix}shift_single") do |shift_to:, shift_from:|
76
- signature.shift_data!(shift_to: shift_to, shift_from: shift_from)
76
+ define_method(:"#{prefix}shift_single") do |shift_to:, shift_from:, bang: false|
77
+ signature.shift_data!(shift_to: shift_to, shift_from: shift_from, bang: bang)
77
78
  end
78
79
  end
79
80
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shiftable
4
- VERSION = "0.5.0"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shiftable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Boling
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-12 00:00:00.000000000 Z
11
+ date: 2021-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord-transactionable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: byebug
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +150,20 @@ dependencies:
136
150
  - - "~>"
137
151
  - !ruby/object:Gem::Version
138
152
  version: '1.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: silent_stream
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1'
139
167
  - !ruby/object:Gem::Dependency
140
168
  name: sqlite3
141
169
  requirement: !ruby/object:Gem::Requirement