shiftable 0.4.1 → 0.5.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: 7792e05d55078e47ad4fec714ebc5aeed51c6373ba64d31e410ab8cdfe01eb44
4
- data.tar.gz: d1435eb2ab87f9e81367d73036d8bf23c270eccb29157cc1eaf364da9ef8fcb2
3
+ metadata.gz: 678fdd1fc65199227c201ecd4346e4c0019c78747d949ac9b4dcba8b38158c24
4
+ data.tar.gz: 3f527ed0b2e70a2e677b006320b3e667d0512ccfcacc5bb76232129b9f37c6d0
5
5
  SHA512:
6
- metadata.gz: 8fc584eefc4ab6e4bbdcbb22a3b856f8baa2dff98daf5eceb149ced8865fa5c1dc020bcf1cde2918df47f3b0545c9c1827e9f369e2131c4bb5498804c1cce37c
7
- data.tar.gz: d7970f2f74a80bdd5a657e89846a5eddd54d7396cd000d3022b0ba1782336b464586ea1391a23af6ff8df6b7d6047715c571cdfb43d55145744504f5ced7e4db
6
+ metadata.gz: c281d086eabfbce50dcd9f3098374656a8c7536b0ce1d5e21d5262c6e205172f90930aa7120a747904d6599313c235adba0bc5487011fa7b5df0e8c90af9157b
7
+ data.tar.gz: 217eb1fb303dbc45ccb962bddbff8c4dc246b20351b1f1f2312b9e4ea8c4db7a90360c089b040f0276e544a38193d01feb1728bd6f3949dfe849509a12d38a64
data/CHANGELOG.md CHANGED
@@ -1,7 +1,28 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.4.0] - 2021-10-27
3
+ ### Added
4
+
5
+
6
+ ### Changed
7
+
8
+
9
+ ### Fixed
10
+
11
+
12
+ ### Removed
13
+
4
14
 
15
+ ## [0.5.0] - 2021-11-12
16
+ ### Added
17
+
18
+ - Support for Polymorphic associations (see examples in specs or README)
19
+
20
+ ## [0.4.1] - 2021-11-10
21
+ ### Fixed
22
+
23
+ - Documentation typos in README
24
+
25
+ ## [0.4.0] - 2021-10-27
5
26
  ### Changed
6
27
 
7
28
  - option :preflight_checks renamed to :precheck
@@ -11,7 +32,6 @@
11
32
  - Even more 100% spec coverage
12
33
 
13
34
  ## [0.3.0] - 2021-10-26
14
-
15
35
  ### Changed
16
36
 
17
37
  - Internal rewrite to improve maintainability
@@ -22,7 +42,6 @@
22
42
  - Even more 100% spec coverage
23
43
 
24
44
  ## [0.2.0] - 2021-10-24
25
-
26
45
  ### Changed
27
46
 
28
47
  - option `before_save` is now `before_shift` as originally documented
@@ -34,7 +53,6 @@
34
53
  - Documentation
35
54
 
36
55
  ## [0.1.1] - 2021-10-23
37
-
38
56
  ### Fixed
39
57
 
40
58
  - Github Actions build
@@ -44,7 +62,6 @@
44
62
  - Linting
45
63
 
46
64
  ## [0.1.0] - 2021-10-23
47
-
48
65
  ### Added
49
66
 
50
67
  - 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
@@ -104,7 +104,7 @@ 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,
@@ -114,7 +114,7 @@ all federation spaceships are commandeered! You are ruined!
114
114
 
115
115
  class Spaceship < ActiveRecord::Base
116
116
  belongs_to :space_federation
117
- extend Shiftable::Collection.new belongs_to: :space_federation, has_one: :spaceship,
117
+ extend Shiftable::Collection.new belongs_to: :space_federation, has_many: :spaceships,
118
118
  before_shift: lambda { |shifting_rel:, shift_to:, shift_from:|
119
119
  shifting_rel.each { |spaceship| spaceship.federation_changes += 1 }
120
120
  }
@@ -129,6 +129,68 @@ class SpaceFederation < ActiveRecord::Base
129
129
  end
130
130
  ```
131
131
 
132
+ ### Polymorphism and has_many through
133
+
134
+ ```ruby
135
+ class SpaceTreaty < ActiveRecord::Base
136
+ has_many :space_treaty_signature
137
+ end
138
+
139
+ class SpaceTreatySignature < ActiveRecord::Base
140
+ belongs_to :space_treaty
141
+ belongs_to :signatory, polymorphic: true
142
+ # When two space federations assimilate (i.e. merge) to form a single larger federation,
143
+ # they become party to (i.e. signatories of) all the treaties that had been signed by either.
144
+ # In practical terms, this means:
145
+ #
146
+ # surviving_federation = SpaceFederation.find(1)
147
+ # assimilated_federation = SpaceFederation.find(2)
148
+ # SpaceTreatySignature.where(
149
+ # signatory_id: assimilated_federation_id,
150
+ # signatory_type: "SpaceFederation"
151
+ # ).update_all(
152
+ # signatory_id: surviving_federation.id
153
+ # )
154
+ extend Shiftable::Collection.new(
155
+ belongs_to: :signatory, has_many: :space_treaty_signature,
156
+ polymorphic_type: "SpaceFederation",
157
+ method_prefix: "space_federation_",
158
+ before_shift: lambda { |shifting_rel:, shift_to:, shift_from:|
159
+ # Each item in shifting_rel is an instance of the class where Shiftable::Collection is defined,
160
+ # in this case: SpaceTreatySignature
161
+ # And each of them has a signatory which is of type "SpaceFederation",
162
+ # because a polymorphic collection only targets one type.
163
+ # shifting_rel.each { |signature| signature.signatory == "SpaceFederation" }
164
+ }
165
+ )
166
+ end
167
+
168
+ class SpaceFederation < ActiveRecord::Base
169
+ has_many :space_treaty_signature, as: :signatory
170
+ has_many :space_treaties, through: :space_treaty_signatures, as: :signatory
171
+ has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
172
+ has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
173
+ def assimilate_from(other_federation)
174
+ SpaceTreatySignature.space_federation_shift_cx(shift_to: self, shift_from: other_federation)
175
+ end
176
+ end
177
+
178
+ # Including Planet and SpaceStation, for completeness of the example as the other "types" of polymorphic signatories
179
+ class Planet < ActiveRecord::Base
180
+ has_many :space_treaty_signature, as: :signatory
181
+ has_many :space_treaties, through: :space_treaty_signatures
182
+ has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
183
+ has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
184
+ end
185
+
186
+ class SpaceStation < ActiveRecord::Base
187
+ has_many :space_treaty_signature, as: :signatory
188
+ has_many :space_treaties, through: :space_treaty_signatures
189
+ has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
190
+ has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
191
+ end
192
+ ```#<--rubocop/md-->#<--rubocop/md-->`
193
+
132
194
  ### Complete example
133
195
 
134
196
  Putting it all together...
@@ -148,7 +210,7 @@ class Spaceship < ActiveRecord::Base
148
210
  before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
149
211
 
150
212
  belongs_to :space_federation
151
- extend Shiftable::Collection.new belongs_to: :space_federation, has_one: :spaceship,
213
+ extend Shiftable::Collection.new belongs_to: :space_federation, has_many: :spaceships,
152
214
  before_shift: lambda { |shifting_rel:, shift_to:, shift_from:|
153
215
  shifting_rel.each { |spaceship| spaceship.federation_changes += 1 }
154
216
  }
@@ -157,11 +219,47 @@ end
157
219
  class SpaceFederation < ActiveRecord::Base
158
220
  has_many :captains
159
221
  has_many :spaceships
222
+ has_many :space_treaty_signature, as: :signatory
223
+ has_many :space_treaties, through: :space_treaty_signatures, as: :signatory
224
+ has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
225
+ has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
226
+
227
+ def assimilate_from(other_federation)
228
+ SpaceTreatySignature.space_federation_shift_cx(shift_to: self, shift_from: other_federation)
229
+ end
160
230
 
161
231
  def all_spaceships_commandeered_by(nemesis_federation)
162
232
  Spaceship.shift_cx(shift_to: nemesis_federation, shift_from: self)
163
233
  end
164
234
  end
235
+ class SpaceTreaty < ActiveRecord::Base
236
+ has_many :space_treaty_signatures
237
+ end
238
+
239
+ class SpaceTreatySignature < ActiveRecord::Base
240
+ belongs_to :space_treaty
241
+ belongs_to :signatory, polymorphic: true
242
+ extend Shiftable::Collection.new(
243
+ belongs_to: :signatory, has_many: :space_treaty_signatures,
244
+ polymorphic_type: "SpaceFederation",
245
+ method_prefix: "space_federation_"
246
+ )
247
+ end
248
+
249
+ # Including Planet and SpaceStation, for completeness of the example as the other "types" of polymorphic signatories
250
+ class Planet < ActiveRecord::Base
251
+ has_many :space_treaty_signatures, as: :signatory
252
+ has_many :space_treaties, through: :space_treaty_signatures
253
+ has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
254
+ has_many :treaty_stations, class_name: "SpaceStation", through: :space_treaty_signatures, as: :signatory
255
+ end
256
+
257
+ class SpaceStation < ActiveRecord::Base
258
+ has_many :space_treaty_signature, as: :signatory
259
+ has_many :space_treaties, through: :space_treaty_signatures
260
+ has_many :treaty_federations, class_name: "SpaceFederation", through: :space_treaty_signatures, as: :signatory
261
+ has_many :treaty_planets, class_name: "Planet", through: :space_treaty_signatures, as: :signatory
262
+ end
165
263
  ```
166
264
 
167
265
  ... 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)
20
20
  # Ruby's Module initializer doesn't take any arguments
21
21
  super()
22
22
 
@@ -35,12 +35,13 @@ 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
42
  before_shift: before_shift
42
43
  },
43
- type: :cx
44
+ type: polymorphic ? :pcx : :cx
44
45
  )
45
46
  end
46
47
 
@@ -62,11 +63,12 @@ module Shiftable
62
63
  module ShiftCollectionModulizer
63
64
  def to_mod(signature)
64
65
  prefix = signature.method_prefix
66
+ type = signature.type
65
67
  Module.new do
66
- define_method(:"#{prefix}shift_cx_column") do
67
- signature.shift_column
68
+ define_method(:"#{prefix}shift_#{type}_column") do
69
+ signature.send("shift_#{type}_column")
68
70
  end
69
- define_method(:"#{prefix}shift_cx") do |shift_to:, shift_from:|
71
+ define_method(:"#{prefix}shift_#{type}") do |shift_to:, shift_from:|
70
72
  signature.shift_data!(shift_to: shift_to, shift_from: shift_from)
71
73
  end
72
74
  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,14 @@ 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 polymorphic_type
28
+ options.dig(:polymorphic, :type)
29
+ end
30
+
31
+ def polymorphic_as
32
+ options.dig(:polymorphic, :as)
33
+ end
34
+
27
35
  def invalid_type?
28
36
  !VALID_TYPES.include?(type)
29
37
  end
@@ -48,21 +56,21 @@ module Shiftable
48
56
  end
49
57
 
50
58
  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
59
+ bt_reflection = base.reflect_on_association(belongs_to)
60
+ raise ArgumentError, "Unable to find belongs_to: :#{belongs_to} in #{base}" unless bt_reflection
61
+ # We can't validate any further if the reflection is polymorphic
62
+ return true if bt_reflection.polymorphic?
63
+
64
+ klass = bt_reflection.klass
65
+ has_reflection = klass.reflect_on_association(has_rel)
66
+ raise ArgumentError, "Unable to find #{has_rel_name}: :#{has_rel} in #{klass}" unless has_reflection
57
67
  end
58
68
 
59
69
  module CxMethods
60
- def has_many
70
+ def has_rel
61
71
  associations[:has_many]
62
72
  end
63
73
 
64
- alias has_rel has_many
65
-
66
74
  def shift_data!(shift_to:, shift_from:)
67
75
  validate_relationships
68
76
  shifting_rel = ShiftingRelation.new(
@@ -77,13 +85,35 @@ module Shiftable
77
85
  end
78
86
  end
79
87
 
88
+ module PcxMethods
89
+ # This method could be defined for parity, but it is never used.
90
+ # def has_rel
91
+ # associations[:has_many]
92
+ # end
93
+
94
+ def shift_data!(shift_to:, shift_from:)
95
+ validate_relationships
96
+ shifting_rel = ShiftingPolymorphicRelation.new(
97
+ to: shift_to,
98
+ from: shift_from,
99
+ column: {
100
+ type: polymorphic_type,
101
+ as: polymorphic_as,
102
+ id_column: shift_pcx_column
103
+ },
104
+ base: base
105
+ )
106
+ shifting_rel.shift do |result|
107
+ before_shift&.call(shifting_rel: result, shift_to: shift_to, shift_from: shift_from)
108
+ end
109
+ end
110
+ end
111
+
80
112
  module SgMethods
81
- def has_one
113
+ def has_rel
82
114
  associations[:has_one]
83
115
  end
84
116
 
85
- alias has_rel has_one
86
-
87
117
  # Do not move record if a record already exists (we are shifting a "has_one" association, after all)
88
118
  def precheck
89
119
  options[:precheck]
@@ -97,7 +127,7 @@ module Shiftable
97
127
  column: shift_column,
98
128
  base: base
99
129
  ) do
100
- !precheck || !shift_to.send(has_one)
130
+ !precheck || !shift_to.send(has_rel)
101
131
  end
102
132
  shifting.shift do |result|
103
133
  before_shift&.call(shifting: result, shift_to: shift_to, shift_from: shift_from)
@@ -122,9 +152,16 @@ module Shiftable
122
152
  options[:before_shift] || DEFAULT_BEFORE_SHIFT
123
153
  end
124
154
 
155
+ def shift_pcx_column
156
+ "#{polymorphic_as}_id"
157
+ end
158
+
125
159
  def shift_column
126
160
  reflection = base.reflect_on_association(belongs_to).klass.reflect_on_association(has_rel)
127
161
  reflection.foreign_key
128
162
  end
163
+
164
+ alias shift_sg_column shift_column
165
+ alias shift_cx_column shift_column
129
166
  end
130
167
  end
File without changes
@@ -0,0 +1,45 @@
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 result if block_given?
32
+ each(&:save) if run_save
33
+ result
34
+ end
35
+
36
+ private
37
+
38
+ def query
39
+ base.where(
40
+ polymorphic_type_column => column[:type],
41
+ polymorphic_id_column => from.id
42
+ )
43
+ end
44
+ end
45
+ end
File without changes
File without changes
@@ -70,7 +70,7 @@ module Shiftable
70
70
  prefix = signature.method_prefix
71
71
  Module.new do
72
72
  define_method(:"#{prefix}shift_column") do
73
- signature.shift_column
73
+ signature.send("shift_#{signature.type}_column")
74
74
  end
75
75
  define_method(:"#{prefix}shift_single") do |shift_to:, shift_from:|
76
76
  signature.shift_data!(shift_to: shift_to, shift_from: shift_from)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shiftable
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.0"
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.5.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-10 00:00:00.000000000 Z
11
+ date: 2021-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -181,6 +181,7 @@ files:
181
181
  - lib/shiftable/collection.rb
182
182
  - lib/shiftable/mod_signature.rb
183
183
  - lib/shiftable/shifting.rb
184
+ - lib/shiftable/shifting_polymorphic_relation.rb
184
185
  - lib/shiftable/shifting_record.rb
185
186
  - lib/shiftable/shifting_relation.rb
186
187
  - lib/shiftable/single.rb