shiftable 0.4.1 → 0.6.1

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: 7792e05d55078e47ad4fec714ebc5aeed51c6373ba64d31e410ab8cdfe01eb44
4
- data.tar.gz: d1435eb2ab87f9e81367d73036d8bf23c270eccb29157cc1eaf364da9ef8fcb2
3
+ metadata.gz: ae22d0c5730e6bde9ad658ee1b8deddb2ca83e8d09cccc560945faf51c796b6a
4
+ data.tar.gz: b8ef6d21e4273b247a4287a31059eb02bed5bafaf3941562ddfc78f798fd139a
5
5
  SHA512:
6
- metadata.gz: 8fc584eefc4ab6e4bbdcbb22a3b856f8baa2dff98daf5eceb149ced8865fa5c1dc020bcf1cde2918df47f3b0545c9c1827e9f369e2131c4bb5498804c1cce37c
7
- data.tar.gz: d7970f2f74a80bdd5a657e89846a5eddd54d7396cd000d3022b0ba1782336b464586ea1391a23af6ff8df6b7d6047715c571cdfb43d55145744504f5ced7e4db
6
+ metadata.gz: 19ab8c6178402cb18788424023d380672a8d4c7597b432703b79f73900950cf353d8fe85702a0fbd09387f3dd61c4276ba69fe0e9e99499bc77b52d8c9bc3b0b
7
+ data.tar.gz: 66d90707da9952a116619e7421127f2c6bad0f4b74558e24ea0da97d7e5670a17960dfabf92ece49ab9cf10bcc60deb9f556f25fc835df656ba9ad1f0f17aa82
data/CHANGELOG.md CHANGED
@@ -1,9 +1,45 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.4.0] - 2021-10-27
3
+ ### Added
4
+
4
5
 
5
6
  ### Changed
6
7
 
8
+
9
+ ### Fixed
10
+
11
+
12
+ ### Removed
13
+
14
+
15
+ ## [0.6.1] - 2021-11-14
16
+ ### Added
17
+
18
+ - Support for using save with bang (`save!`) to raise error when ActiveRecord save fails.
19
+
20
+ ## [0.6.0] - 2021-11-12
21
+ ### Added
22
+
23
+ - Support for wrappers/hooks around each record shift, and around the entire set (see examples in specs or README)
24
+
25
+ ## [0.5.1] - 2021-11-12
26
+ ### Fixed
27
+
28
+ - Documentation typos in README
29
+
30
+ ## [0.5.0] - 2021-11-12
31
+ ### Added
32
+
33
+ - Support for Polymorphic associations (see examples in specs or README)
34
+
35
+ ## [0.4.1] - 2021-11-10
36
+ ### Fixed
37
+
38
+ - Documentation typos in README
39
+
40
+ ## [0.4.0] - 2021-10-27
41
+ ### Changed
42
+
7
43
  - option :preflight_checks renamed to :precheck
8
44
 
9
45
  ### Added
@@ -11,7 +47,6 @@
11
47
  - Even more 100% spec coverage
12
48
 
13
49
  ## [0.3.0] - 2021-10-26
14
-
15
50
  ### Changed
16
51
 
17
52
  - Internal rewrite to improve maintainability
@@ -22,7 +57,6 @@
22
57
  - Even more 100% spec coverage
23
58
 
24
59
  ## [0.2.0] - 2021-10-24
25
-
26
60
  ### Changed
27
61
 
28
62
  - option `before_save` is now `before_shift` as originally documented
@@ -34,7 +68,6 @@
34
68
  - Documentation
35
69
 
36
70
  ## [0.1.1] - 2021-10-23
37
-
38
71
  ### Fixed
39
72
 
40
73
  - Github Actions build
@@ -44,7 +77,6 @@
44
77
  - Linting
45
78
 
46
79
  ## [0.1.0] - 2021-10-23
47
-
48
80
  ### Added
49
81
 
50
82
  - Initial release
data/CODE_OF_CONDUCT.md CHANGED
File without changes
data/CONTRIBUTING.md CHANGED
File without changes
data/LICENSE.txt CHANGED
File without changes
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
 
@@ -104,18 +104,17 @@ end
104
104
 
105
105
  This works as you would expect with STI (single table inheritance) classes, i.e. when defined on a subclass, only the records of that class get shifted.
106
106
 
107
- ### Multiple association on a single class
107
+ ### Multiple associations on a single class
108
108
 
109
109
  What if the captain and the spaceship have a boss... the space
110
110
  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
- extend Shiftable::Collection.new belongs_to: :space_federation, has_one: :spaceship,
118
- before_shift: lambda { |shifting_rel:, shift_to:, shift_from:|
116
+ extend Shiftable::Collection.new belongs_to: :space_federation, has_many: :spaceships,
117
+ before_shift: lambda { |shifting_rel|
119
118
  shifting_rel.each { |spaceship| spaceship.federation_changes += 1 }
120
119
  }
121
120
  end
@@ -129,6 +128,112 @@ class SpaceFederation < ActiveRecord::Base
129
128
  end
130
129
  ```
131
130
 
131
+ ### Polymorphism and has_many through
132
+
133
+ ```ruby
134
+ class SpaceTreaty < ActiveRecord::Base
135
+ has_many :space_treaty_signature
136
+ end
137
+
138
+ class SpaceTreatySignature < ActiveRecord::Base
139
+ belongs_to :space_treaty
140
+ belongs_to :signatory, polymorphic: true
141
+ # When two space federations assimilate (i.e. merge) to form a single larger federation,
142
+ # they become party to (i.e. signatories of) all the treaties that had been signed by either.
143
+ # In practical terms, this means:
144
+ #
145
+ # surviving_federation = SpaceFederation.find(1)
146
+ # assimilated_federation = SpaceFederation.find(2)
147
+ # SpaceTreatySignature.where(
148
+ # signatory_id: assimilated_federation_id,
149
+ # signatory_type: "SpaceFederation"
150
+ # ).update_all(
151
+ # signatory_id: surviving_federation.id
152
+ # )
153
+ extend Shiftable::Collection.new(
154
+ belongs_to: :signatory, has_many: :space_treaty_signature,
155
+ polymorphic: { type: "SpaceFederation", as: :signatory },
156
+ method_prefix: "space_federation_",
157
+ before_shift: lambda { |shifting_rel|
158
+ # Each item in shifting_rel is an instance of the class where Shiftable::Collection is defined,
159
+ # in this case: SpaceTreatySignature
160
+ # And each of them has a signatory which is of type "SpaceFederation",
161
+ # because a polymorphic collection only targets one type.
162
+ # shifting_rel.each { |signature| signature.signatory == "SpaceFederation" }
163
+ }
164
+ )
165
+ end
166
+
167
+ class SpaceFederation < ActiveRecord::Base
168
+ has_many :space_treaty_signature, as: :signatory
169
+ has_many :space_treaties, through: :space_treaty_signatures, as: :signatory
170
+ has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
171
+ has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
172
+ def assimilate_from(other_federation)
173
+ SpaceTreatySignature.space_federation_shift_pcx(shift_to: self, shift_from: other_federation)
174
+ end
175
+ end
176
+
177
+ # Including Planet and SpaceStation, for completeness of the example as the other "types" of polymorphic signatories
178
+ class Planet < ActiveRecord::Base
179
+ has_many :space_treaty_signature, as: :signatory
180
+ has_many :space_treaties, through: :space_treaty_signatures
181
+ has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
182
+ has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
183
+ end
184
+
185
+ class SpaceStation < ActiveRecord::Base
186
+ has_many :space_treaty_signature, as: :signatory
187
+ has_many :space_treaties, through: :space_treaty_signatures
188
+ has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
189
+ has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
190
+ end
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 { |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
+
132
237
  ### Complete example
133
238
 
134
239
  Putting it all together...
@@ -145,23 +250,78 @@ end
145
250
  class Spaceship < ActiveRecord::Base
146
251
  belongs_to :captain
147
252
  extend Shiftable::Single.new belongs_to: :captain, has_one: :spaceship, precheck: true,
148
- before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
253
+ before_shift: ->(shifting_rel) { shifting_rel.result.ownership_changes += 1 }
149
254
 
150
255
  belongs_to :space_federation
151
- extend Shiftable::Collection.new belongs_to: :space_federation, has_one: :spaceship,
152
- before_shift: lambda { |shifting_rel:, shift_to:, shift_from:|
153
- shifting_rel.each { |spaceship| spaceship.federation_changes += 1 }
154
- }
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
+ )
155
279
  end
156
280
 
157
281
  class SpaceFederation < ActiveRecord::Base
158
282
  has_many :captains
159
283
  has_many :spaceships
284
+ has_many :space_treaty_signature, as: :signatory
285
+ has_many :space_treaties, through: :space_treaty_signatures, as: :signatory
286
+ has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
287
+ has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
288
+
289
+ def assimilate_from(other_federation)
290
+ SpaceTreatySignature.space_federation_shift_cx(shift_to: self, shift_from: other_federation)
291
+ end
160
292
 
161
293
  def all_spaceships_commandeered_by(nemesis_federation)
162
294
  Spaceship.shift_cx(shift_to: nemesis_federation, shift_from: self)
163
295
  end
164
296
  end
297
+ class SpaceTreaty < ActiveRecord::Base
298
+ has_many :space_treaty_signatures
299
+ end
300
+
301
+ class SpaceTreatySignature < ActiveRecord::Base
302
+ belongs_to :space_treaty
303
+ belongs_to :signatory, polymorphic: true
304
+ extend Shiftable::Collection.new(
305
+ belongs_to: :signatory, has_many: :space_treaty_signatures,
306
+ polymorphic: { type: "SpaceFederation", as: :signatory },
307
+ method_prefix: "space_federation_"
308
+ )
309
+ end
310
+
311
+ # Including Planet and SpaceStation, for completeness of the example as the other "types" of polymorphic signatories
312
+ class Planet < ActiveRecord::Base
313
+ has_many :space_treaty_signatures, as: :signatory
314
+ has_many :space_treaties, through: :space_treaty_signatures
315
+ has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
316
+ has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
317
+ end
318
+
319
+ class SpaceStation < ActiveRecord::Base
320
+ has_many :space_treaty_signature, as: :signatory
321
+ has_many :space_treaties, through: :space_treaty_signatures
322
+ has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
323
+ has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
324
+ end
165
325
  ```
166
326
 
167
327
  ... stay tuned!
@@ -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:, 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
 
@@ -35,12 +35,18 @@ module Shiftable
35
35
  has_many: has_many.to_s.to_sym
36
36
  },
37
37
  options: {
38
+ polymorphic: polymorphic,
38
39
  method_prefix: method_prefix,
39
40
  # will prevent the save if it returns false
40
41
  # allows for any custom logic to be run, such as setting attributes, prior to the shift (save).
41
- 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
42
48
  },
43
- type: :cx
49
+ type: polymorphic ? :pcx : :cx
44
50
  )
45
51
  end
46
52
 
@@ -62,12 +68,13 @@ module Shiftable
62
68
  module ShiftCollectionModulizer
63
69
  def to_mod(signature)
64
70
  prefix = signature.method_prefix
71
+ type = signature.type
65
72
  Module.new do
66
- define_method(:"#{prefix}shift_cx_column") do
67
- signature.shift_column
73
+ define_method(:"#{prefix}shift_#{type}_column") do
74
+ signature.send("shift_#{type}_column")
68
75
  end
69
- define_method(:"#{prefix}shift_cx") do |shift_to:, shift_from:|
70
- 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)
71
78
  end
72
79
  end
73
80
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Shiftable
4
4
  class ModSignature
5
- VALID_TYPES = %i[sg cx].freeze
6
- VALID_ASSOCIATIONS = { sg: %i[belongs_to has_one], cx: %i[belongs_to has_many] }.freeze
5
+ VALID_ASSOCIATIONS = { sg: %i[belongs_to has_one], cx: %i[belongs_to has_many], pcx: %i[belongs_to has_many] }.freeze
6
+ VALID_TYPES = VALID_ASSOCIATIONS.keys.dup.freeze
7
7
  DEFAULT_BEFORE_SHIFT = ->(*_) { true }
8
8
  attr_reader :associations, :options, :type, :base
9
9
 
@@ -24,6 +24,18 @@ 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
+
31
+ def polymorphic_type
32
+ options.dig(:polymorphic, :type)
33
+ end
34
+
35
+ def polymorphic_as
36
+ options.dig(:polymorphic, :as)
37
+ end
38
+
27
39
  def invalid_type?
28
40
  !VALID_TYPES.include?(type)
29
41
  end
@@ -48,59 +60,87 @@ module Shiftable
48
60
  end
49
61
 
50
62
  def validate_relationships
51
- bt = base.reflect_on_association(belongs_to)
52
- raise ArgumentError, "Unable to find belongs_to: :#{belongs_to} in #{base}" unless bt
53
-
54
- klass = bt.klass
55
- hr = klass.reflect_on_association(has_rel)
56
- raise ArgumentError, "Unable to find #{has_rel_name}: :#{has_rel} in #{klass}" unless hr
63
+ bt_reflection = base.reflect_on_association(belongs_to)
64
+ raise ArgumentError, "Unable to find belongs_to: :#{belongs_to} in #{base}" unless bt_reflection
65
+ # We can't validate any further if the reflection is polymorphic
66
+ return true if bt_reflection.polymorphic?
67
+
68
+ klass = bt_reflection.klass
69
+ has_reflection = klass.reflect_on_association(has_rel)
70
+ raise ArgumentError, "Unable to find #{has_rel_name}: :#{has_rel} in #{klass}" unless has_reflection
57
71
  end
58
72
 
59
73
  module CxMethods
60
- def has_many
74
+ def has_rel
61
75
  associations[:has_many]
62
76
  end
63
77
 
64
- alias has_rel has_many
65
-
66
- def shift_data!(shift_to:, shift_from:)
78
+ def shift_data!(shift_to:, shift_from:, bang: false)
67
79
  validate_relationships
68
80
  shifting_rel = ShiftingRelation.new(
69
81
  to: shift_to,
70
82
  from: shift_from,
71
83
  column: shift_column,
72
- base: base
84
+ base: base,
85
+ wrapper: wrapper,
86
+ bang: bang
73
87
  )
74
- shifting_rel.shift do |result|
75
- 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)
90
+ end
91
+ end
92
+ end
93
+
94
+ module PcxMethods
95
+ # This method could be defined for parity, but it is never used.
96
+ # def has_rel
97
+ # associations[:has_many]
98
+ # end
99
+
100
+ def shift_data!(shift_to:, shift_from:, bang: false)
101
+ validate_relationships
102
+ shifting_rel = ShiftingPolymorphicRelation.new(
103
+ to: shift_to,
104
+ from: shift_from,
105
+ column: {
106
+ type: polymorphic_type,
107
+ as: polymorphic_as,
108
+ id_column: shift_pcx_column
109
+ },
110
+ base: base,
111
+ wrapper: wrapper,
112
+ bang: bang
113
+ )
114
+ shifting_rel.shift do
115
+ before_shift&.call(shifting_rel)
76
116
  end
77
117
  end
78
118
  end
79
119
 
80
120
  module SgMethods
81
- def has_one
121
+ def has_rel
82
122
  associations[:has_one]
83
123
  end
84
124
 
85
- alias has_rel has_one
86
-
87
125
  # Do not move record if a record already exists (we are shifting a "has_one" association, after all)
88
126
  def precheck
89
127
  options[:precheck]
90
128
  end
91
129
 
92
- def shift_data!(shift_to:, shift_from:)
130
+ def shift_data!(shift_to:, shift_from:, bang: false)
93
131
  validate_relationships
94
132
  shifting = ShiftingRecord.new(
95
133
  to: shift_to,
96
134
  from: shift_from,
97
135
  column: shift_column,
98
- base: base
136
+ base: base,
137
+ wrapper: wrapper,
138
+ bang: bang
99
139
  ) do
100
- !precheck || !shift_to.send(has_one)
140
+ !precheck || !shift_to.send(has_rel)
101
141
  end
102
- shifting.shift do |result|
103
- before_shift&.call(shifting: result, shift_to: shift_to, shift_from: shift_from)
142
+ shifting.shift do
143
+ before_shift&.call(shifting)
104
144
  end
105
145
  end
106
146
  end
@@ -122,9 +162,16 @@ module Shiftable
122
162
  options[:before_shift] || DEFAULT_BEFORE_SHIFT
123
163
  end
124
164
 
165
+ def shift_pcx_column
166
+ "#{polymorphic_as}_id"
167
+ end
168
+
125
169
  def shift_column
126
170
  reflection = base.reflect_on_association(belongs_to).klass.reflect_on_association(has_rel)
127
171
  reflection.foreign_key
128
172
  end
173
+
174
+ alias shift_sg_column shift_column
175
+ alias shift_cx_column shift_column
129
176
  end
130
177
  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(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
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shiftable
4
+ # Gets data to be shifted
5
+ class ShiftingPolymorphicRelation < Shifting
6
+ include Enumerable
7
+
8
+ def polymorphic_id_column
9
+ column[:id_column]
10
+ end
11
+
12
+ def polymorphic_type_column
13
+ "#{column[:as]}_type"
14
+ end
15
+
16
+ def found?
17
+ result.any?
18
+ end
19
+
20
+ def each(&block)
21
+ result.each(&block)
22
+ end
23
+
24
+ # @return result (once it is shifted)
25
+ def shift
26
+ return false unless found?
27
+
28
+ each do |record|
29
+ record.send("#{polymorphic_id_column}=", to.id)
30
+ end
31
+ @run_save = yield if block_given?
32
+ return result unless run_save
33
+
34
+ run_save!
35
+ result
36
+ end
37
+
38
+ private
39
+
40
+ def query
41
+ base.where(
42
+ polymorphic_type_column => column[:type],
43
+ polymorphic_id_column => from.id
44
+ )
45
+ end
46
+ end
47
+ end
@@ -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
+ 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
  )
@@ -70,10 +71,10 @@ module Shiftable
70
71
  prefix = signature.method_prefix
71
72
  Module.new do
72
73
  define_method(:"#{prefix}shift_column") do
73
- signature.shift_column
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.4.1"
4
+ VERSION = "0.6.1"
5
5
  end
data/lib/shiftable.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "shiftable/version"
4
4
  require_relative "shiftable/shifting"
5
5
  require_relative "shiftable/shifting_record"
6
+ require_relative "shiftable/shifting_polymorphic_relation"
6
7
  require_relative "shiftable/shifting_relation"
7
8
  require_relative "shiftable/mod_signature"
8
9
  require_relative "shiftable/collection"
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.4.1
4
+ version: 0.6.1
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-10 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
@@ -181,6 +209,7 @@ files:
181
209
  - lib/shiftable/collection.rb
182
210
  - lib/shiftable/mod_signature.rb
183
211
  - lib/shiftable/shifting.rb
212
+ - lib/shiftable/shifting_polymorphic_relation.rb
184
213
  - lib/shiftable/shifting_record.rb
185
214
  - lib/shiftable/shifting_relation.rb
186
215
  - lib/shiftable/single.rb