shiftable 0.3.0 → 0.4.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: 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