shiftable 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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