shiftable 0.3.0 → 0.4.0
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 +4 -4
- data/CHANGELOG.md +18 -1
- data/README.md +6 -4
- data/lib/shiftable/collection.rb +3 -2
- data/lib/shiftable/mod_signature.rb +41 -72
- data/lib/shiftable/shifting.rb +38 -0
- data/lib/shiftable/shifting_record.rb +25 -0
- data/lib/shiftable/shifting_relation.rb +34 -0
- data/lib/shiftable/single.rb +6 -5
- data/lib/shiftable/version.rb +1 -1
- data/lib/shiftable.rb +3 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62926c718edd257728210a0870ea5eca44aeb35b33a929d87d2efb8a5f902910
|
4
|
+
data.tar.gz: b4c35fcc2a3eff45d992b5260e6d4f2f13d870782f40d06285172eb2f1207f9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c21a1f0c51b4a3cb715730fe07bb29d562a84d8dd79b558a3ca06c6a7bf20032928225b41aa1e749b4a7a85afe4fdb33cbf9d068b7539ef622665272f1f8115
|
7
|
+
data.tar.gz: d9bc2d54fe6fb3f15f781b33ddf83c6abfa91f7b66e426274bc9ef07595e61a9c6b4f42a2ffe27e6b8cce5453dbcc0221ac07261bddb762a3a9eba3e79d7cef1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.4.0] - 2021-10-27
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
|
7
|
+
- option :preflight_checks renamed to :precheck
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Even more 100% spec coverage
|
12
|
+
|
3
13
|
## [0.3.0] - 2021-10-26
|
4
14
|
|
5
15
|
### Changed
|
@@ -42,4 +52,11 @@
|
|
42
52
|
- 100% test coverage
|
43
53
|
|
44
54
|
[0.1.0]: https://github.com/pboling/shiftable/releases/tag/v0.1.0
|
45
|
-
|
55
|
+
|
56
|
+
[0.1.1]: https://github.com/pboling/shiftable/releases/tag/v0.1.1
|
57
|
+
|
58
|
+
[0.2.0]: https://github.com/pboling/shiftable/releases/tag/v0.2.0
|
59
|
+
|
60
|
+
[0.3.0]: https://github.com/pboling/shiftable/releases/tag/v0.3.0
|
61
|
+
|
62
|
+
[0.4.0]: https://github.com/pboling/shiftable/releases/tag/v0.4.0
|
data/README.md
CHANGED
@@ -87,7 +87,7 @@ But how can you accomplish this? If you used the `shiftable` gem, won't take but
|
|
87
87
|
|
88
88
|
class Spaceship < ActiveRecord::Base
|
89
89
|
belongs_to :captain
|
90
|
-
extend Shiftable::Single.new belongs_to: :captain, has_one: :spaceship,
|
90
|
+
extend Shiftable::Single.new belongs_to: :captain, has_one: :spaceship, precheck: true,
|
91
91
|
before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
|
92
92
|
end
|
93
93
|
```
|
@@ -100,8 +100,10 @@ class Spaceship < ActiveRecord::Base
|
|
100
100
|
belongs_to :captain
|
101
101
|
|
102
102
|
class << self
|
103
|
-
include Shiftable::Single.new belongs_to: :captain, has_one: :spaceship,
|
104
|
-
before_shift:
|
103
|
+
include Shiftable::Single.new belongs_to: :captain, has_one: :spaceship, precheck: true,
|
104
|
+
before_shift: lambda { |shifting:, shift_to:, shift_from:|
|
105
|
+
shifting.ownership_changes += 1
|
106
|
+
}
|
105
107
|
end
|
106
108
|
end
|
107
109
|
```
|
@@ -150,7 +152,7 @@ end
|
|
150
152
|
|
151
153
|
class Spaceship < ActiveRecord::Base
|
152
154
|
belongs_to :captain
|
153
|
-
extend Shiftable::Single.new belongs_to: :captain, has_one: :spaceship,
|
155
|
+
extend Shiftable::Single.new belongs_to: :captain, has_one: :spaceship, precheck: true,
|
154
156
|
before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
|
155
157
|
|
156
158
|
belongs_to :space_federation
|
data/lib/shiftable/collection.rb
CHANGED
@@ -61,11 +61,12 @@ module Shiftable
|
|
61
61
|
# Creates anonymous Ruby Modules, containing dynamically built methods
|
62
62
|
module ShiftCollectionModulizer
|
63
63
|
def to_mod(signature)
|
64
|
+
prefix = signature.method_prefix
|
64
65
|
Module.new do
|
65
|
-
define_method(:"#{
|
66
|
+
define_method(:"#{prefix}shift_cx_column") do
|
66
67
|
signature.shift_column
|
67
68
|
end
|
68
|
-
define_method(:"#{
|
69
|
+
define_method(:"#{prefix}shift_cx") do |shift_to:, shift_from:|
|
69
70
|
signature.shift_data!(shift_to: shift_to, shift_from: shift_from)
|
70
71
|
end
|
71
72
|
end
|
@@ -7,8 +7,8 @@ module Shiftable
|
|
7
7
|
sg: %i[belongs_to has_one],
|
8
8
|
cx: %i[belongs_to has_many]
|
9
9
|
}.freeze
|
10
|
-
|
11
|
-
attr_reader :base
|
10
|
+
DEFAULT_BEFORE_SHIFT = ->(*_) { true }
|
11
|
+
attr_reader :associations, :options, :type, :base
|
12
12
|
|
13
13
|
# Imagine you are a Spaceship Captain, the Spaceship belongs_to you, and it has only one Captain.
|
14
14
|
# But you have to sell it to your nemesis!
|
@@ -22,12 +22,21 @@ module Shiftable
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def validate
|
25
|
-
raise ArgumentError, "type must be one of: #{VALID_TYPES}, provided: #{type}"
|
26
|
-
raise ArgumentError, "associations must be symbols" if
|
27
|
-
raise ArgumentError, "exactly two distinct associations must be provided"
|
25
|
+
raise ArgumentError, "type must be one of: #{VALID_TYPES}, provided: #{type}" if invalid_type?
|
26
|
+
raise ArgumentError, "associations must be symbols" if invalid_association_key_type?
|
27
|
+
raise ArgumentError, "exactly two distinct associations must be provided" if invalid_number_of_associations?
|
28
|
+
end
|
29
|
+
|
30
|
+
def invalid_type?
|
31
|
+
!VALID_TYPES.include?(type)
|
32
|
+
end
|
28
33
|
|
29
|
-
|
30
|
-
|
34
|
+
def invalid_association_key_type?
|
35
|
+
associations.keys.detect { |key| !key.is_a?(Symbol) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def invalid_number_of_associations?
|
39
|
+
associations.keys.uniq.length != 2
|
31
40
|
end
|
32
41
|
|
33
42
|
# @note Chainable
|
@@ -45,8 +54,9 @@ module Shiftable
|
|
45
54
|
bt = base.reflect_on_association(belongs_to)
|
46
55
|
raise ArgumentError, "Unable to find belongs_to: :#{belongs_to} in #{base}" unless bt
|
47
56
|
|
48
|
-
|
49
|
-
|
57
|
+
klass = bt.klass
|
58
|
+
hr = klass.reflect_on_association(has_rel)
|
59
|
+
raise ArgumentError, "Unable to find #{has_rel_name}: :#{has_rel} in #{klass}" unless hr
|
50
60
|
end
|
51
61
|
|
52
62
|
module CxMethods
|
@@ -56,30 +66,17 @@ module Shiftable
|
|
56
66
|
|
57
67
|
alias has_rel has_many
|
58
68
|
|
59
|
-
# returns nil or ActiveRecord::Relation object
|
60
|
-
def data_for_shift(id)
|
61
|
-
base.where(send("shift_column") => id) if super
|
62
|
-
end
|
63
|
-
|
64
|
-
# returns false, nil or ActiveRecord::Relation object
|
65
|
-
def data_for_shift_safe(shift_to:, shift_from:)
|
66
|
-
return false unless super
|
67
|
-
|
68
|
-
data_for_shift(shift_from.id)
|
69
|
-
end
|
70
|
-
|
71
69
|
def shift_data!(shift_to:, shift_from:)
|
72
70
|
validate_relationships
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
71
|
+
shifting_rel = ShiftingRelation.new(
|
72
|
+
to: shift_to,
|
73
|
+
from: shift_from,
|
74
|
+
column: shift_column,
|
75
|
+
base: base
|
76
|
+
)
|
77
|
+
shifting_rel.shift do |result|
|
78
|
+
before_shift&.call(shifting_rel: result, shift_to: shift_to, shift_from: shift_from)
|
79
79
|
end
|
80
|
-
before_shift&.call(shifting_rel: shifting_rel, shift_to: shift_to, shift_from: shift_from)
|
81
|
-
shifting_rel.each(&:save)
|
82
|
-
shifting_rel
|
83
80
|
end
|
84
81
|
end
|
85
82
|
|
@@ -91,35 +88,23 @@ module Shiftable
|
|
91
88
|
alias has_rel has_one
|
92
89
|
|
93
90
|
# Do not move record if a record already exists (we are shifting a "has_one" association, after all)
|
94
|
-
def
|
95
|
-
options[:
|
96
|
-
end
|
97
|
-
|
98
|
-
# returns nil or ActiveRecord object
|
99
|
-
def data_for_shift(id)
|
100
|
-
base.find_by(send("shift_column") => id) if super
|
101
|
-
end
|
102
|
-
|
103
|
-
# returns false, nil or ActiveRecord object
|
104
|
-
def data_for_shift_safe(shift_to:, shift_from:)
|
105
|
-
return false unless super
|
106
|
-
|
107
|
-
if preflight_checks
|
108
|
-
already_exists = shift_to.send(has_one)
|
109
|
-
return false if already_exists
|
110
|
-
end
|
111
|
-
data_for_shift(shift_from.id)
|
91
|
+
def precheck
|
92
|
+
options[:precheck]
|
112
93
|
end
|
113
94
|
|
114
95
|
def shift_data!(shift_to:, shift_from:)
|
115
96
|
validate_relationships
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
97
|
+
shifting = ShiftingRecord.new(
|
98
|
+
to: shift_to,
|
99
|
+
from: shift_from,
|
100
|
+
column: shift_column,
|
101
|
+
base: base
|
102
|
+
) do
|
103
|
+
!precheck || !shift_to.send(has_one)
|
104
|
+
end
|
105
|
+
shifting.shift do |result|
|
106
|
+
before_shift&.call(shifting: result, shift_to: shift_to, shift_from: shift_from)
|
107
|
+
end
|
123
108
|
end
|
124
109
|
end
|
125
110
|
|
@@ -134,31 +119,15 @@ module Shiftable
|
|
134
119
|
options[:method_prefix]
|
135
120
|
end
|
136
121
|
|
137
|
-
alias mepr method_prefix
|
138
|
-
|
139
122
|
# will prevent the save if it returns false
|
140
123
|
# allows for any custom logic to be run, such as setting shift_from attributes, prior to the shift is saved.
|
141
124
|
def before_shift
|
142
|
-
options[:before_shift]
|
125
|
+
options[:before_shift] || DEFAULT_BEFORE_SHIFT
|
143
126
|
end
|
144
127
|
|
145
128
|
def shift_column
|
146
129
|
reflection = base.reflect_on_association(belongs_to).klass.reflect_on_association(has_rel)
|
147
130
|
reflection.foreign_key
|
148
131
|
end
|
149
|
-
|
150
|
-
# Effect is to short-circuit data_for_shift method prior to executing the ActiveRecord query
|
151
|
-
# if there is no ID, to avoid a full table scan when no id provided,
|
152
|
-
# e.g. where(id: nil).
|
153
|
-
def data_for_shift(id)
|
154
|
-
true if id
|
155
|
-
end
|
156
|
-
|
157
|
-
# Effect is to short-circuit data_for_shift_safe method prior to executing the ActiveRecord query
|
158
|
-
# if there is no ID, to avoid a full table scan when no id provided,
|
159
|
-
# e.g. where(id: nil).
|
160
|
-
def data_for_shift_safe(shift_to:, shift_from:)
|
161
|
-
true if shift_from&.id && shift_to&.id
|
162
|
-
end
|
163
132
|
end
|
164
133
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shiftable
|
4
|
+
# Gets data to be shifted
|
5
|
+
class Shifting
|
6
|
+
attr_reader :to, :from, :column, :base, :result, :run_save
|
7
|
+
|
8
|
+
def initialize(to:, from:, column:, base:)
|
9
|
+
@to = to
|
10
|
+
@from = from
|
11
|
+
@column = column
|
12
|
+
@base = base
|
13
|
+
validate
|
14
|
+
do_query = block_given? ? yield : true
|
15
|
+
@result = do_query ? query : nil
|
16
|
+
@run_save = true
|
17
|
+
end
|
18
|
+
|
19
|
+
# def found?
|
20
|
+
# raise "found? must be defined in a subclass"
|
21
|
+
# end
|
22
|
+
|
23
|
+
# def shift
|
24
|
+
# raise "shift must be defined in a subclass"
|
25
|
+
# end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def validate
|
30
|
+
raise ArgumentError, "shift_to must have an id (primary key) value, but is: #{to&.id}" unless to&.id
|
31
|
+
raise ArgumentError, "shift_from must have an id (primary key) value, but is: #{from&.id}" unless from&.id
|
32
|
+
end
|
33
|
+
|
34
|
+
# def query
|
35
|
+
# raise "query must be defined in a subclass"
|
36
|
+
# end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shiftable
|
4
|
+
# Gets data to be shifted
|
5
|
+
class ShiftingRecord < Shifting
|
6
|
+
def found?
|
7
|
+
!!result
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return true, false
|
11
|
+
def shift
|
12
|
+
return false unless found?
|
13
|
+
|
14
|
+
result.send("#{column}=", to.id)
|
15
|
+
@run_save = yield result if block_given?
|
16
|
+
result.save if run_save
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def query
|
22
|
+
base.find_by(column => from.id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shiftable
|
4
|
+
# Gets data to be shifted
|
5
|
+
class ShiftingRelation < Shifting
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def found?
|
9
|
+
result.any?
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
result.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return result (once it is shifted)
|
17
|
+
def shift
|
18
|
+
return false unless found?
|
19
|
+
|
20
|
+
each do |record|
|
21
|
+
record.send("#{column}=", to.id)
|
22
|
+
end
|
23
|
+
@run_save = yield result if block_given?
|
24
|
+
each(&:save) if run_save
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def query
|
31
|
+
base.where(column => from.id)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/shiftable/single.rb
CHANGED
@@ -13,7 +13,7 @@
|
|
13
13
|
# extend Shiftable::Single.new(
|
14
14
|
# belongs_to: :captain,
|
15
15
|
# has_one: :spaceship,
|
16
|
-
#
|
16
|
+
# precheck: true,
|
17
17
|
# before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
|
18
18
|
# )
|
19
19
|
# end
|
@@ -21,7 +21,7 @@
|
|
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,
|
24
|
+
def initialize(belongs_to:, has_one:, method_prefix: nil, precheck: true, before_shift: nil)
|
25
25
|
# Ruby's Module initializer doesn't take any arguments
|
26
26
|
super()
|
27
27
|
|
@@ -40,7 +40,7 @@ module Shiftable
|
|
40
40
|
},
|
41
41
|
options: {
|
42
42
|
# Do not move record if a record already exists (we are shifting a "has_one" association, after all)
|
43
|
-
|
43
|
+
precheck: precheck,
|
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).
|
@@ -67,11 +67,12 @@ module Shiftable
|
|
67
67
|
# Creates anonymous Ruby Modules, containing dynamically built methods
|
68
68
|
module ShiftSingleModulizer
|
69
69
|
def to_mod(signature)
|
70
|
+
prefix = signature.method_prefix
|
70
71
|
Module.new do
|
71
|
-
define_method(:"#{
|
72
|
+
define_method(:"#{prefix}shift_column") do
|
72
73
|
signature.shift_column
|
73
74
|
end
|
74
|
-
define_method(:"#{
|
75
|
+
define_method(:"#{prefix}shift_single") do |shift_to:, shift_from:|
|
75
76
|
signature.shift_data!(shift_to: shift_to, shift_from: shift_from)
|
76
77
|
end
|
77
78
|
end
|
data/lib/shiftable/version.rb
CHANGED
data/lib/shiftable.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "shiftable/version"
|
4
|
+
require_relative "shiftable/shifting"
|
5
|
+
require_relative "shiftable/shifting_record"
|
6
|
+
require_relative "shiftable/shifting_relation"
|
4
7
|
require_relative "shiftable/mod_signature"
|
5
8
|
require_relative "shiftable/collection"
|
6
9
|
require_relative "shiftable/single"
|
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
|
+
version: 0.4.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-10-
|
11
|
+
date: 2021-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -276,6 +276,20 @@ dependencies:
|
|
276
276
|
- - "~>"
|
277
277
|
- !ruby/object:Gem::Version
|
278
278
|
version: '0.21'
|
279
|
+
- !ruby/object:Gem::Dependency
|
280
|
+
name: simplecov-cobertura
|
281
|
+
requirement: !ruby/object:Gem::Requirement
|
282
|
+
requirements:
|
283
|
+
- - "~>"
|
284
|
+
- !ruby/object:Gem::Version
|
285
|
+
version: '1.4'
|
286
|
+
type: :development
|
287
|
+
prerelease: false
|
288
|
+
version_requirements: !ruby/object:Gem::Requirement
|
289
|
+
requirements:
|
290
|
+
- - "~>"
|
291
|
+
- !ruby/object:Gem::Version
|
292
|
+
version: '1.4'
|
279
293
|
- !ruby/object:Gem::Dependency
|
280
294
|
name: sqlite3
|
281
295
|
requirement: !ruby/object:Gem::Requirement
|
@@ -319,6 +333,9 @@ files:
|
|
319
333
|
- lib/shiftable.rb
|
320
334
|
- lib/shiftable/collection.rb
|
321
335
|
- lib/shiftable/mod_signature.rb
|
336
|
+
- lib/shiftable/shifting.rb
|
337
|
+
- lib/shiftable/shifting_record.rb
|
338
|
+
- lib/shiftable/shifting_relation.rb
|
322
339
|
- lib/shiftable/single.rb
|
323
340
|
- lib/shiftable/version.rb
|
324
341
|
homepage: https://railsbling.com/tags/shiftable
|