shiftable 0.4.1 → 0.6.1

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: 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