shiftable 0.5.1 → 0.6.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: 8bad3e2f27677202648655a227fa9f251a595910db9146c9fdb72952657053d3
4
- data.tar.gz: 481fede6aa751831cec5c19fe8849b61b081f51cfd4e5aa0a7522ecbb31b38bf
3
+ metadata.gz: 03d919c64ab0be03ba40c5474cfa7971dda6158ab01df20940bb644c32482b19
4
+ data.tar.gz: 3246e28e7578ea7b56ae905c23c675e121a8c71556d127b8ee4a0fbeedcce15b
5
5
  SHA512:
6
- metadata.gz: 050f4afb2b1ad1e81cd9209dfee0a0f1270745a02bfea84dcaff3914794349c4e6b53b01fb21873731ad05693ae0d02639ee3ade2a0f0d1b5b103fc30952e362
7
- data.tar.gz: 5bfc201b247b3082a0a1836c164133f73f59f317de9664934f08bc28a6b3d00140e2c2a3fe0ed7da2a0450fcabb67c66eb28e28c3787403517eadd28fb8d8114
6
+ metadata.gz: b3721c0d437b1ab45deb99f71f7a199fe4c37aae31f86f8443eb2effd1bd07c9d930aa64c4a4b824c7371863c9cb219251e59dc673d9ef40a73551e75c7efc67
7
+ data.tar.gz: c0d40a890f89fda5137575d9052131605f33e6a861e6b9e55b6681d89cfeba4f9ac5547cb09c4ab68cb69bafa50a9c6ebc75412152cf8c3bf9e76f90ce80f823
data/CHANGELOG.md CHANGED
@@ -12,6 +12,11 @@
12
12
  ### Removed
13
13
 
14
14
 
15
+ ## [0.6.0] - 2021-11-12
16
+ ### Added
17
+
18
+ - Support for wrappers/hooks around each record shift, and around the entire set (see examples in specs or README)
19
+
15
20
  ## [0.5.1] - 2021-11-12
16
21
  ### Fixed
17
22
 
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
@@ -155,7 +154,7 @@ class SpaceTreatySignature < ActiveRecord::Base
155
154
  belongs_to: :signatory, has_many: :space_treaty_signature,
156
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",
@@ -191,6 +190,50 @@ class SpaceStation < ActiveRecord::Base
191
190
  end
192
191
  ```
193
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 { |record, &block|
209
+ tresult = record.transaction_wrapper(outside_rescued_errors: ActiveRecord::RecordNotUnique) do
210
+ puts "melon #{record.name} honey"
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
+ ```
236
+
194
237
  ### Complete example
195
238
 
196
239
  Putting it all together...
@@ -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 { |record, &block|
264
+ tresult = record.transaction_wrapper(outside_rescued_errors: ActiveRecord::RecordNotUnique) do
265
+ puts "melon #{record.name} honey"
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
@@ -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
  )
@@ -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
@@ -77,10 +81,11 @@ module Shiftable
77
81
  to: shift_to,
78
82
  from: shift_from,
79
83
  column: shift_column,
80
- base: base
84
+ base: base,
85
+ wrapper: wrapper
81
86
  )
82
- shifting_rel.shift do |result|
83
- before_shift&.call(shifting_rel: result, shift_to: shift_to, shift_from: shift_from)
87
+ shifting_rel.shift do
88
+ before_shift&.call(shifting_rel)
84
89
  end
85
90
  end
86
91
  end
@@ -101,10 +106,11 @@ module Shiftable
101
106
  as: polymorphic_as,
102
107
  id_column: shift_pcx_column
103
108
  },
104
- base: base
109
+ base: base,
110
+ wrapper: wrapper
105
111
  )
106
- shifting_rel.shift do |result|
107
- before_shift&.call(shifting_rel: result, shift_to: shift_to, shift_from: shift_from)
112
+ shifting_rel.shift do
113
+ before_shift&.call(shifting_rel)
108
114
  end
109
115
  end
110
116
  end
@@ -125,12 +131,13 @@ module Shiftable
125
131
  to: shift_to,
126
132
  from: shift_from,
127
133
  column: shift_column,
128
- base: base
134
+ base: base,
135
+ wrapper: wrapper
129
136
  ) do
130
137
  !precheck || !shift_to.send(has_rel)
131
138
  end
132
- shifting.shift do |result|
133
- before_shift&.call(shifting: result, shift_to: shift_to, shift_from: shift_from)
139
+ shifting.shift do
140
+ before_shift&.call(shifting)
134
141
  end
135
142
  end
136
143
  end
@@ -3,9 +3,9 @@
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
+ attr_reader :to, :from, :column, :base, :result, :run_save, :shift_all_wrapper, :shift_each_wrapper
7
7
 
8
- def initialize(to:, from:, column:, base:)
8
+ def initialize(to:, from:, column:, base:, wrapper:)
9
9
  @to = to
10
10
  @from = from
11
11
  @column = column
@@ -14,6 +14,8 @@ module Shiftable
14
14
  do_query = block_given? ? yield : true
15
15
  @result = do_query ? query : nil
16
16
  @run_save = true
17
+ @shift_all_wrapper = wrapper[:all]
18
+ @shift_each_wrapper = wrapper[:each]
17
19
  end
18
20
 
19
21
  # def found?
@@ -31,6 +33,28 @@ module Shiftable
31
33
  raise ArgumentError, "shift_from must have an id (primary key) value, but is: #{from&.id}" unless from&.id
32
34
  end
33
35
 
36
+ def run_save!
37
+ if shift_all_wrapper
38
+ shift_all_wrapper.call(self) do
39
+ do_saves
40
+ end
41
+ else
42
+ do_saves
43
+ end
44
+ end
45
+
46
+ def do_saves
47
+ if shift_each_wrapper
48
+ each do |rec|
49
+ shift_each_wrapper.call(rec) do
50
+ rec.save
51
+ end
52
+ end
53
+ else
54
+ each(&:save)
55
+ end
56
+ end
57
+
34
58
  # def query
35
59
  # raise "query must be defined in a subclass"
36
60
  # 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(result) do
36
+ result.save
37
+ end
38
+ else
39
+ 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
  )
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shiftable
4
- VERSION = "0.5.1"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shiftable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Boling
@@ -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