shiftable 0.5.0 → 0.7.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: 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