subroutine 1.0.1 → 2.0.0.beta

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: a8871d701ab81402f275e38e7bf632c629bffc4320d31040119d99b12db05caf
4
- data.tar.gz: edba79a803523a74e4e53d5e9cf62bf0dbdb2087592e7503b7b2d18395e5a8a6
3
+ metadata.gz: d0ade77ec5abb9601e1dce7bc53e34baac35c978a41aef3268b57ee5b9def6e5
4
+ data.tar.gz: c450947886efc34b26b202b52202909f44735a2d5f26a3c1af51fdf93be509c6
5
5
  SHA512:
6
- metadata.gz: 9953dd7a8ac11f4c842773c88a338e255c254bbf38f50a3437f204406a8136eca15544cd87aa9641395faf933ca9a00ddf9b3951a2a76326302e677988240143
7
- data.tar.gz: 900b42b891947630463f3700a5df207d5e689a91dbe2e1970987f41b565fac7889083161d9d39bfd36082a7d0f4d1667d9b8bd549e7804371c58ad2987d238a0
6
+ metadata.gz: c171a9bb82076ed4f5e407375d19e423a5e6bba0211192109d300e3b7372d537a0bac3d67ef5c480ef9af34ddd2a849a07fef79459f5597ea97e195cad239fe7
7
+ data.tar.gz: 52502e8dd504de2d59faa4665ab7c755c9ff6ba8b951e79acaa264c4d257f09a20da58977f1565e8d121023d6a2f5291cd4528eca143d697252b2f6a27d4197b
@@ -74,7 +74,7 @@ module Subroutine
74
74
  class_eval <<-EV, __FILE__, __LINE__ + 1
75
75
  try(:silence_redefinition_of_method, :#{config.foreign_type_method})
76
76
  def #{config.foreign_type_method}
77
- #{config.inferred_class_name.inspect}
77
+ #{config.inferred_foreign_type.inspect}
78
78
  end
79
79
  EV
80
80
  end
@@ -119,7 +119,7 @@ module Subroutine
119
119
  if config&.behavior == :association
120
120
  maybe_raise_on_association_type_mismatch!(config, value)
121
121
  set_field(config.foreign_type_method, value&.class&.name, opts) if config.polymorphic?
122
- set_field(config.foreign_key_method, value&.id, opts)
122
+ set_field(config.foreign_key_method, value&.send(config.find_by), opts)
123
123
  association_cache[config.field_name] = value
124
124
  else
125
125
  if config&.behavior == :association_component
@@ -137,10 +137,7 @@ module Subroutine
137
137
  stored_result = association_cache[config.field_name]
138
138
  return stored_result unless stored_result.nil?
139
139
 
140
- fk = config.field_reader? ? send(config.foreign_key_method) : get_field(config.foreign_id_method)
141
- type = config.field_reader? || !config.polymorphic? ? send(config.foreign_type_method) : get_field(config.foreign_type_method)
142
-
143
- result = fetch_association_instance(type, fk, config.unscoped?)
140
+ result = fetch_association_instance(config)
144
141
  association_cache[config.field_name] = result
145
142
  else
146
143
  get_field_without_association(field_name)
@@ -175,25 +172,32 @@ module Subroutine
175
172
  end
176
173
  end
177
174
 
178
- def fetch_association_instance(_type, _fk, _unscoped = false)
179
- return nil unless _type && _fk
175
+ def fetch_association_instance(config)
176
+ klass =
177
+ if config.field_reader?
178
+ config.polymorphic? ? send(config.foreign_type_method) : config.inferred_foreign_type
179
+ else
180
+ get_field(config.foreign_type_method)
181
+ end
180
182
 
181
- klass = _type
182
183
  klass = klass.classify.constantize if klass.is_a?(String)
183
-
184
184
  return nil unless klass
185
185
 
186
+ foreign_key = config.foreign_key_method
187
+ value = send(foreign_key)
188
+ return nil unless value
189
+
186
190
  scope = klass.all
187
- scope = scope.unscoped if _unscoped
191
+ scope = scope.unscoped if config.unscoped?
188
192
 
189
- scope.find(_fk)
193
+ scope.find_by!(config.find_by => value)
190
194
  end
191
195
 
192
196
  def maybe_raise_on_association_type_mismatch!(config, record)
193
197
  return if config.polymorphic?
194
198
  return if record.nil?
195
199
 
196
- klass = config.inferred_class_name.constantize
200
+ klass = config.inferred_foreign_type.constantize
197
201
 
198
202
  return if record.class <= klass || record.class >= klass
199
203
 
@@ -10,7 +10,7 @@ module Subroutine
10
10
  def validate!
11
11
  super
12
12
 
13
- if as && foreign_key
13
+ if config[:as] && foreign_key
14
14
  raise ArgumentError, ":as and :foreign_key options should not be provided together to an association invocation"
15
15
  end
16
16
  end
@@ -34,12 +34,13 @@ module Subroutine
34
34
  config[:as] || field_name
35
35
  end
36
36
 
37
- def class_name
38
- config[:class_name]&.to_s
37
+ def foreign_type
38
+ (config[:foreign_type] || config[:class_name])&.to_s
39
39
  end
40
+ alias class_name foreign_type
40
41
 
41
- def inferred_class_name
42
- class_name || as.to_s.camelize
42
+ def inferred_foreign_type
43
+ foreign_type || as.to_s.camelize
43
44
  end
44
45
 
45
46
  def foreign_key
@@ -54,8 +55,12 @@ module Subroutine
54
55
  foreign_key_method.to_s.gsub(/_id$/, "_type").to_sym
55
56
  end
56
57
 
58
+ def find_by
59
+ (config[:find_by] || :id).to_sym
60
+ end
61
+
57
62
  def build_foreign_key_field
58
- build_child_field(foreign_key_method, type: :integer)
63
+ build_child_field(foreign_key_method)
59
64
  end
60
65
 
61
66
  def build_foreign_type_field
@@ -7,7 +7,7 @@ module Subroutine
7
7
  class Configuration < ::SimpleDelegator
8
8
 
9
9
  PROTECTED_GROUP_IDENTIFIERS = %i[all original default].freeze
10
- INHERITABLE_OPTIONS = %i[mass_assignable field_reader field_writer groups].freeze
10
+ INHERITABLE_OPTIONS = %i[mass_assignable field_reader field_writer groups aka].freeze
11
11
  NO_GROUPS = [].freeze
12
12
 
13
13
  def self.from(field_name, options)
@@ -100,6 +100,7 @@ module Subroutine
100
100
  groups = nil if groups == false
101
101
  opts[:groups] = Array(groups).map(&:to_sym).presence
102
102
  opts.delete(:group)
103
+ opts[:aka] = opts[:aka].to_sym if opts[:aka]
103
104
  opts
104
105
  end
105
106
 
data/lib/subroutine/op.rb CHANGED
@@ -37,28 +37,11 @@ module Subroutine
37
37
  op
38
38
  end
39
39
 
40
- protected
41
-
42
- def field(field_name, options = {})
43
- result = super(field_name, options)
44
-
45
- if options[:aka]
46
- Array(options[:aka]).each do |as|
47
- self._error_map = _error_map.merge(as.to_sym => field_name.to_sym)
48
- end
49
- end
50
-
51
- result
52
- end
53
-
54
40
  end
55
41
 
56
42
  class_attribute :_failure_class
57
43
  self._failure_class = Subroutine::Failure
58
44
 
59
- class_attribute :_error_map
60
- self._error_map = {}
61
-
62
45
  def initialize(inputs = {})
63
46
  setup_fields(inputs)
64
47
  setup_outputs
@@ -82,7 +65,7 @@ module Subroutine
82
65
 
83
66
  if errors.empty?
84
67
  validate_outputs!
85
- true
68
+ self
86
69
  else
87
70
  raise _failure_class, self
88
71
  end
@@ -91,6 +74,7 @@ module Subroutine
91
74
  # the action which should be invoked upon form submission (from the controller)
92
75
  def submit
93
76
  submit!
77
+ true
94
78
  rescue Exception => e
95
79
  if e.respond_to?(:record)
96
80
  inherit_errors(e.record) unless e.record == self
@@ -127,18 +111,23 @@ module Subroutine
127
111
  raise NotImplementedError
128
112
  end
129
113
 
130
- # applies the errors in error_object to self
131
- # returns false so failure cases can end with this invocation
132
- def inherit_errors(error_object)
114
+ def inherit_errors(error_object, prefix: nil)
133
115
  error_object = error_object.errors if error_object.respond_to?(:errors)
134
116
 
135
- error_object.each do |k, v|
136
- if respond_to?(k)
137
- errors.add(k, v)
138
- elsif _error_map[k.to_sym]
139
- errors.add(_error_map[k.to_sym], v)
117
+ error_object.each do |field_name, error|
118
+ field_name = "#{prefix}#{field_name}" if prefix
119
+ field_name = field_name.to_sym
120
+
121
+ field_config = get_field_config(field_name)
122
+ field_config ||= begin
123
+ kv = field_configurations.find { |_k, config| config[:aka] == field_name }
124
+ kv ? kv.last : nil
125
+ end
126
+
127
+ if field_config
128
+ errors.add(field_config.field_name, error)
140
129
  else
141
- errors.add(:base, error_object.full_message(k, v))
130
+ errors.add(:base, error_object.full_message(field_name, error))
142
131
  end
143
132
  end
144
133
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Subroutine
4
4
 
5
- MAJOR = 1
5
+ MAJOR = 2
6
6
  MINOR = 0
7
- PATCH = 1
8
- PRE = nil
7
+ PATCH = 0
8
+ PRE = "beta"
9
9
 
10
10
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
11
11
 
@@ -23,11 +23,21 @@ module Subroutine
23
23
  assert_equal doug.id, op.user_id
24
24
  end
25
25
 
26
+ def test_it_can_be_nil
27
+ op = SimpleAssociationOp.new user: nil
28
+ assert_nil op.user
29
+ assert_nil op.user_id
30
+
31
+ op = SimpleAssociationOp.new
32
+ assert_nil op.user
33
+ assert_nil op.user_id
34
+ end
35
+
26
36
  def test_it_looks_up_an_association
27
37
  all_mock = mock
28
38
 
29
39
  ::User.expects(:all).returns(all_mock)
30
- all_mock.expects(:find).with(1).returns(doug)
40
+ all_mock.expects(:find_by!).with(id: 1).returns(doug)
31
41
 
32
42
  op = SimpleAssociationOp.new user_type: "User", user_id: doug.id
33
43
  assert_equal doug, op.user
@@ -37,7 +47,7 @@ module Subroutine
37
47
  all_mock = mock
38
48
 
39
49
  ::User.expects(:all).returns(all_mock)
40
- all_mock.expects(:find).with(1).returns(doug)
50
+ all_mock.expects(:find_by!).with(id: 1).returns(doug)
41
51
 
42
52
  op = SimpleAssociationOp.new user_id: doug.id
43
53
  assert_equal doug, op.user
@@ -49,7 +59,7 @@ module Subroutine
49
59
 
50
60
  ::User.expects(:all).returns(all_mock)
51
61
  all_mock.expects(:unscoped).returns(unscoped_mock)
52
- unscoped_mock.expects(:find).with(1).returns(doug)
62
+ unscoped_mock.expects(:find_by!).with(id: 1).returns(doug)
53
63
 
54
64
  op = UnscopedSimpleAssociationOp.new user_id: doug.id
55
65
  assert_equal doug, op.user
@@ -59,7 +69,7 @@ module Subroutine
59
69
  all_mock = mock
60
70
  ::User.expects(:all).never
61
71
  ::AdminUser.expects(:all).returns(all_mock)
62
- all_mock.expects(:find).with(1).returns(doug)
72
+ all_mock.expects(:find_by!).with(id: 1).returns(doug)
63
73
 
64
74
  op = PolymorphicAssociationOp.new(admin_type: "AdminUser", admin_id: doug.id)
65
75
  assert_equal doug, op.admin
@@ -70,11 +80,60 @@ module Subroutine
70
80
  assert_equal "AdminUser", op.admin_type
71
81
  end
72
82
 
83
+ def test_it_allows_foreign_key_to_be_set
84
+ all_mock = mock
85
+ ::User.expects(:all).returns(all_mock)
86
+ all_mock.expects(:find_by!).with(id: "foobarbaz").returns(doug)
87
+
88
+ op = ::AssociationWithForeignKeyOp.new(user_identifier: "foobarbaz")
89
+ assert_equal doug, op.user
90
+ assert_equal "user_identifier", op.field_configurations[:user][:foreign_key]
91
+ end
92
+
93
+ def test_it_allows_a_foreign_key_and_find_by_to_be_set
94
+ all_mock = mock
95
+ ::User.expects(:all).returns(all_mock)
96
+ all_mock.expects(:find_by!).with(email_address: doug.email_address).returns(doug)
97
+
98
+ op = ::AssociationWithFindByAndForeignKeyOp.new(email_address: doug.email_address)
99
+ assert_equal doug, op.user
100
+ assert_equal "email_address", op.field_configurations[:user][:find_by]
101
+ end
102
+
103
+ def test_it_allows_a_find_by_to_be_set
104
+ all_mock = mock
105
+ ::User.expects(:all).returns(all_mock)
106
+ all_mock.expects(:find_by!).with(email_address: doug.email_address).returns(doug)
107
+
108
+ op = ::AssociationWithFindByKeyOp.new(user_id: doug.email_address)
109
+ assert_equal doug, op.user
110
+ assert_equal "email_address", op.field_configurations[:user][:find_by]
111
+ end
112
+
113
+ def test_values_are_correct_for_find_by_usage
114
+ op = ::AssociationWithFindByKeyOp.new(user: doug)
115
+ assert_equal doug, op.user
116
+ assert_equal doug.email_address, op.user_id
117
+ end
118
+
119
+ def test_values_are_correct_for_foreign_key_usage
120
+ op = ::AssociationWithForeignKeyOp.new(user: doug)
121
+ assert_equal doug, op.user
122
+ assert_equal doug.id, op.user_identifier
123
+ end
124
+
125
+ def test_values_are_correct_for_both_foreign_key_and_find_by_usage
126
+ op = ::AssociationWithFindByAndForeignKeyOp.new(user: doug)
127
+ assert_equal doug, op.user
128
+ assert_equal doug.email_address, op.email_address
129
+ assert_equal false, op.respond_to?(:user_id)
130
+ end
131
+
73
132
  def test_it_inherits_associations_via_fields_from
74
133
  all_mock = mock
75
134
 
76
135
  ::User.expects(:all).returns(all_mock)
77
- all_mock.expects(:find).with(1).returns(doug)
136
+ all_mock.expects(:find_by!).with(id: 1).returns(doug)
78
137
 
79
138
  op = ::InheritedSimpleAssociation.new(user_type: "User", user_id: doug.id)
80
139
  assert_equal doug, op.user
@@ -88,7 +147,7 @@ module Subroutine
88
147
 
89
148
  ::User.expects(:all).returns(all_mock)
90
149
  all_mock.expects(:unscoped).returns(unscoped_mock)
91
- unscoped_mock.expects(:find).with(1).returns(doug)
150
+ unscoped_mock.expects(:find_by!).with(id: 1).returns(doug)
92
151
 
93
152
  op = ::InheritedUnscopedAssociation.new(user_type: "User", user_id: doug.id)
94
153
  assert_equal doug, op.user
@@ -100,7 +159,7 @@ module Subroutine
100
159
  all_mock = mock
101
160
  ::User.expects(:all).never
102
161
  ::AdminUser.expects(:all).returns(all_mock)
103
- all_mock.expects(:find).with(1).returns(doug)
162
+ all_mock.expects(:find_by!).with(id: 1).returns(doug)
104
163
 
105
164
  op = ::InheritedPolymorphicAssociationOp.new(admin_type: "AdminUser", admin_id: doug.id)
106
165
  assert_equal doug, op.admin
@@ -127,6 +127,13 @@ module Subroutine
127
127
  assert_equal ["has gotta be @admin.com"], op.errors[:email]
128
128
  end
129
129
 
130
+ def test_validation_errors_can_be_inherited_and_prefixed
131
+ op = PrefixedInputsOp.new(user_email_address: "foo@bar.com")
132
+ refute op.submit
133
+
134
+ assert_equal ["has gotta be @admin.com"], op.errors[:user_email_address]
135
+ end
136
+
130
137
  def test_when_valid_perform_completes_it_returns_control
131
138
  op = ::SignupOp.new(email: "foo@bar.com", password: "password123")
132
139
  op.submit!
@@ -139,6 +146,11 @@ module Subroutine
139
146
  assert_equal "foo@bar.com", u.email_address
140
147
  end
141
148
 
149
+ def test_instance_and_class_submit_bang_return_instance
150
+ assert ::SignupOp.new(email: "foo@bar.com", password: "password123").submit!.is_a?(::SignupOp)
151
+ assert ::SignupOp.submit!(email: "foo@bar.com", password: "password123").is_a?(::SignupOp)
152
+ end
153
+
142
154
  def test_it_raises_an_error_when_used_with_a_bang_and_performing_or_validation_fails
143
155
  op = ::SignupOp.new(email: "foo@bar.com")
144
156
 
data/test/support/ops.rb CHANGED
@@ -23,6 +23,14 @@ class User
23
23
  new(id: id)
24
24
  end
25
25
 
26
+ def self.find_by(params)
27
+ new(params)
28
+ end
29
+
30
+ def self.find_by!(params)
31
+ find_by(params) || raise
32
+ end
33
+
26
34
  end
27
35
 
28
36
  class AdminUser < ::User
@@ -304,6 +312,24 @@ class AssociationWithClassOp < ::OpWithAssociation
304
312
 
305
313
  end
306
314
 
315
+ class AssociationWithForeignKeyOp < ::OpWithAssociation
316
+
317
+ association :user, foreign_key: "user_identifier"
318
+
319
+ end
320
+
321
+ class AssociationWithFindByKeyOp < ::OpWithAssociation
322
+
323
+ association :user, find_by: "email_address"
324
+
325
+ end
326
+
327
+ class AssociationWithFindByAndForeignKeyOp < ::OpWithAssociation
328
+
329
+ association :user, foreign_key: "email_address", find_by: "email_address"
330
+
331
+ end
332
+
307
333
  class ExceptAssociationOp < ::Subroutine::Op
308
334
 
309
335
  fields_from ::PolymorphicAssociationOp, except: %i[admin]
@@ -447,3 +473,15 @@ class CustomFailureClassOp < ::Subroutine::Op
447
473
  end
448
474
 
449
475
  end
476
+
477
+ class PrefixedInputsOp < ::Subroutine::Op
478
+
479
+ string :user_email_address
480
+
481
+ def perform
482
+ u = AdminUser.new(email_address: user_email_address)
483
+ u.valid?
484
+ inherit_errors(u, prefix: :user_)
485
+ end
486
+
487
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subroutine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Nelson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-21 00:00:00.000000000 Z
11
+ date: 2021-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -214,9 +214,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
214
214
  version: '0'
215
215
  required_rubygems_version: !ruby/object:Gem::Requirement
216
216
  requirements:
217
- - - ">="
217
+ - - ">"
218
218
  - !ruby/object:Gem::Version
219
- version: '0'
219
+ version: 1.3.1
220
220
  requirements: []
221
221
  rubygems_version: 3.0.6
222
222
  signing_key: