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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15d27c7c2706faf797241101fa5229f4bf707b06f4d1b7c839cc66fd7ab2ecf8
4
- data.tar.gz: 0fa0fbe15096d109c2d63568101ad0589b4c684775b69b95e831289b8bfdac3d
3
+ metadata.gz: 62926c718edd257728210a0870ea5eca44aeb35b33a929d87d2efb8a5f902910
4
+ data.tar.gz: b4c35fcc2a3eff45d992b5260e6d4f2f13d870782f40d06285172eb2f1207f9d
5
5
  SHA512:
6
- metadata.gz: fa80c525bb2a0cfa99a82350da151412972f86a6699e1091e520d9cec40466f07789e5d1e593f8e3b720cc9e447d742acf28f2c09f5b02c61745cf8ca79084cf
7
- data.tar.gz: 4abc0b5c410ba1748646122914ac768829984934e1f57b4d0483db18d0d5985b1976d69deeceabb3bf04fc5b4e3b6307056e3eeae2cffbd89e8099059cb32c07
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
- [0.1.1]: https://github.com/pboling/shiftable/releases/tag/v0.1.1
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, preflight_checks: true,
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, preflight_checks: true,
104
- before_shift: ->(shifting:, shift_to:, shift_from:) { shifting.ownership_changes += 1 }
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, preflight_checks: true,
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
@@ -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(:"#{signature.mepr}shift_cx_column") do
66
+ define_method(:"#{prefix}shift_cx_column") do
66
67
  signature.shift_column
67
68
  end
68
- define_method(:"#{signature.mepr}shift_cx") do |shift_to:, shift_from:|
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
- attr_accessor :associations, :options, :type
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}" unless VALID_TYPES.include?(type)
26
- raise ArgumentError, "associations must be symbols" if associations.keys.detect { |a| !a.is_a?(Symbol) }
27
- raise ArgumentError, "exactly two distinct associations must be provided" unless associations.keys.uniq.length == 2
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
- invalid_tokens = associations.keys - VALID_ASSOCIATIONS[type]
30
- raise ArgumentError, "valid associations: #{VALID_ASSOCIATIONS[type]}, invalid: #{invalid_tokens}" if invalid_tokens.any?
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
- hr = bt.klass.reflect_on_association(has_rel)
49
- raise ArgumentError, "Unable to find #{has_rel_name}: :#{has_rel} in #{bt.klass}" unless hr
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
- shifting_rel = data_for_shift_safe(shift_to: shift_to, shift_from: shift_from)
75
- return false unless shifting_rel && shifting_rel.any?
76
-
77
- shifting_rel.each do |shifting|
78
- shifting.send("#{send(:shift_column)}=", shift_to.id)
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 preflight_checks
95
- options[:preflight_checks]
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
- shifting = data_for_shift_safe(shift_to: shift_to, shift_from: shift_from)
118
- return false unless shifting
119
-
120
- shifting.send("#{send(:shift_column)}=", shift_to.id)
121
- shifting.save if before_shift.nil? || before_shift.call(shifting: shifting, shift_to: shift_to,
122
- shift_from: shift_from)
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
@@ -13,7 +13,7 @@
13
13
  # extend Shiftable::Single.new(
14
14
  # belongs_to: :captain,
15
15
  # has_one: :spaceship,
16
- # preflight_checks: true,
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, preflight_checks: true, before_shift: 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
- preflight_checks: preflight_checks,
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(:"#{signature.mepr}shift_column") do
72
+ define_method(:"#{prefix}shift_column") do
72
73
  signature.shift_column
73
74
  end
74
- define_method(:"#{signature.mepr}shift_single") do |shift_to:, shift_from:|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shiftable
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
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.3.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-26 00:00:00.000000000 Z
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