praxis 2.0.pre.19 → 2.0.pre.22

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Praxis::Mapper::Resources::TypedMethods do
6
+ let(:resource_class) { TypedResource }
7
+
8
+ context '._finalize!' do
9
+ # The class is already finalized by loading the TypedResource from the spec_resources
10
+ # So we will simply check that all the right things are created
11
+ it 'builds the MethodSignatures constant within the class' do
12
+ expect(TypedResource::MethodSignatures).to_not be_nil
13
+ end
14
+
15
+ it 'builds the inner class type for the defined signatures' do
16
+ expect(TypedResource::MethodSignatures::UpdateBang).to_not be_nil
17
+ expect(TypedResource::MethodSignatures::UpdateBang).to be TypedResource.signature(:update!)
18
+
19
+ expect(TypedResource::MethodSignatures::SelfCreate).to_not be_nil
20
+ expect(TypedResource::MethodSignatures::SelfCreate).to be TypedResource.signature(:'self.create')
21
+ end
22
+
23
+ it 'Subsitutes a ! for a Bang when creating the constant' do
24
+ expect(TypedResource::MethodSignatures::UpdateBang).to be TypedResource.signature(:update!)
25
+ end
26
+
27
+ it 'defines the coercing methods' do
28
+ expect(TypedResource.methods).to include(:_coerce_params_for_class_create)
29
+ expect(TypedResource.instance_methods).to include(:_coerce_params_for_update!)
30
+ end
31
+ end
32
+
33
+ context '.signature' do
34
+ # We are not creating more classes and signatures, simply checking that the ones created
35
+ # for TypedResource in the spec_resource_files are correctly processed
36
+ it 'defines it in the @signatures hash' do
37
+ expect(TypedResource.signatures.keys).to match_array(
38
+ %i[
39
+ self.create
40
+ self.singlearg_create
41
+ create
42
+ update!
43
+ singlearg_update!
44
+ ]
45
+ )
46
+ expect(TypedResource.signature(:'self.create')).to be < Attributor::Struct
47
+ expect(TypedResource.signature(:create)).to be < Attributor::Struct
48
+ expect(TypedResource.signature(:update!)).to be < Attributor::Struct
49
+ end
50
+
51
+ it 'holds the right definition for class create' do
52
+ definition = TypedResource.signature(:'self.create')
53
+ expect(definition.attributes.keys).to eq %i[name payload]
54
+ expect(definition.attributes[:payload].attributes.keys).to eq %i[string_param struct_param]
55
+ end
56
+
57
+ it 'holds the right definition for instance create' do
58
+ definition = TypedResource.signature(:create)
59
+ expect(definition.attributes.keys).to eq %i[id]
60
+ end
61
+ it 'holds the right definition for update!' do
62
+ definition = TypedResource.signature(:update!)
63
+ expect(definition.attributes.keys).to eq %i[string_param struct_param]
64
+ expect(definition.attributes[:struct_param].attributes.keys).to eq %i[id]
65
+ end
66
+ end
67
+
68
+ context 'coerce_params_for' do
69
+ let(:resource_class) do
70
+ Class.new(Praxis::Mapper::Resource) do
71
+ include Praxis::Mapper::Resources::TypedMethods
72
+ def imethod_args(args)
73
+ args
74
+ end
75
+
76
+ def self.cmethod_args(args)
77
+ args
78
+ end
79
+
80
+ def imethod_kwargs(**args)
81
+ args
82
+ end
83
+
84
+ def self.cmethod_kwargs(**args)
85
+ args
86
+ end
87
+ end
88
+ end
89
+
90
+ let(:hook_coercer) { methods.each { |method| resource_class.coerce_params_for(method, type) } }
91
+ # Note, we're associating the same type signature for both imethod and cmethod signatures!
92
+ let(:type) do
93
+ Class.new(Attributor::Struct) do
94
+ attributes do
95
+ attribute :id, Integer, required: true
96
+ attribute :name, String, null: false
97
+ end
98
+ end
99
+ end
100
+
101
+ before do
102
+ # None of our wrappers before invoking the function
103
+ our_wrappers = resource_class.methods.select { |m| m.to_s =~ /^_coerce_params_for_class_/ }
104
+ our_wrappers += resource_class.instance_methods.select { |m| m.to_s =~ /^_coerce_params_for_/ }
105
+ expect(our_wrappers).to be_empty
106
+ end
107
+ context 'instance methods' do
108
+ let(:methods) { %i[imethod_args imethod_kwargs] }
109
+ it 'creates the wrapper methods' do
110
+ hook_coercer
111
+ iwrappers = resource_class.instance_methods.select { |m| m.to_s =~ /^_coerce_params_for_/ }
112
+ expect(iwrappers).to match_array %i[_coerce_params_for_imethod_args _coerce_params_for_imethod_kwargs]
113
+ end
114
+
115
+ it 'sets an around callback for them' do
116
+ hook_coercer
117
+ expect(resource_class.around_callbacks[:imethod_args]).to eq([:_coerce_params_for_imethod_args])
118
+ expect(resource_class.around_callbacks[:imethod_kwargs]).to eq([:_coerce_params_for_imethod_kwargs])
119
+ end
120
+
121
+ context 'when hooking in the callbacks' do
122
+ before do
123
+ hook_coercer
124
+ resource_class._finalize!
125
+ end
126
+ context 'calls the wrapper to validate and load' do
127
+ it 'fails if invalid (id is required)' do
128
+ expect do
129
+ resource_class.new(nil).imethod_args({ name: 'Praxis' })
130
+ end.to raise_error(
131
+ Praxis::Mapper::Resources::IncompatibleTypeForMethodArguments,
132
+ /.imethod_args.id is required/
133
+ )
134
+ expect do
135
+ resource_class.new(nil).imethod_kwargs(name: 'Praxis')
136
+ end.to raise_error(
137
+ Praxis::Mapper::Resources::IncompatibleTypeForMethodArguments,
138
+ /.imethod_kwargs.id is required/
139
+ )
140
+ end
141
+
142
+ it 'succeeds and returns the coerced struct if compatible' do
143
+ result = resource_class.new(nil).imethod_args({ id: '1', name: 'Praxis' })
144
+ expect(result[:id]).to eq(1) # Coerces to Integer!
145
+ expect(result[:name]).to eq('Praxis')
146
+ result = resource_class.new(nil).imethod_kwargs(id: '1', name: 'Praxis')
147
+ expect(result[:id]).to eq(1) # Coerces to Integer!
148
+ expect(result[:name]).to eq('Praxis')
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ context 'class methods' do
155
+ let(:methods) { %i[self.cmethod_args self.cmethod_kwargs] }
156
+ it 'creates the wrapper methods' do
157
+ hook_coercer
158
+ cwrappers = resource_class.methods.select { |m| m.to_s =~ /^_coerce_params_for_class_/ }
159
+ expect(cwrappers).to match_array %i[_coerce_params_for_class_cmethod_args _coerce_params_for_class_cmethod_kwargs]
160
+ end
161
+
162
+ it 'sets an around callback for them' do
163
+ hook_coercer
164
+ expect(resource_class.around_callbacks[:'self.cmethod_args']).to eq([:_coerce_params_for_class_cmethod_args])
165
+ expect(resource_class.around_callbacks[:'self.cmethod_kwargs']).to eq([:_coerce_params_for_class_cmethod_kwargs])
166
+ end
167
+
168
+ context 'when hooking in the callbacks' do
169
+ before do
170
+ hook_coercer
171
+ resource_class._finalize!
172
+ end
173
+ context 'calls the wrapper to validate and load' do
174
+ it 'fails if invalid (id is required)' do
175
+ expect do
176
+ resource_class.cmethod_args({ name: 'Praxis' })
177
+ end.to raise_error(
178
+ Praxis::Mapper::Resources::IncompatibleTypeForMethodArguments,
179
+ /.cmethod_args.id is required/
180
+ )
181
+ expect do
182
+ resource_class.cmethod_kwargs(name: 'Praxis')
183
+ end.to raise_error(
184
+ Praxis::Mapper::Resources::IncompatibleTypeForMethodArguments,
185
+ /.cmethod_kwargs.id is required/
186
+ )
187
+ end
188
+
189
+ it 'succeeds and returns the coerced struct if compatible' do
190
+ result = resource_class.cmethod_args({ id: '1', name: 'Praxis' })
191
+ expect(result[:id]).to eq(1) # Coerces to Integer!
192
+ expect(result[:name]).to eq('Praxis')
193
+ result = resource_class.cmethod_kwargs(id: '1', name: 'Praxis')
194
+ expect(result[:id]).to eq(1) # Coerces to Integer!
195
+ expect(result[:name]).to eq('Praxis')
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -61,9 +61,15 @@ describe Praxis::Response do
61
61
  end
62
62
 
63
63
  it 'should raise an error' do
64
+ resp = nil
64
65
  expect do
65
- response.validate(action)
66
- end.to raise_error(Praxis::Exceptions::Validation, /response definition with that name can be found/)
66
+ resp = response.validate(action)
67
+ end.to_not raise_error
68
+
69
+ expect(resp.status).to eq(500)
70
+ expect(resp.body[:name]).to eq('ValidationError')
71
+ expect(resp.body[:summary]).to eq('Error validating response')
72
+ expect(resp.body[:errors]).to include(/response definition with that name can be found/)
67
73
  end
68
74
  end
69
75
 
@@ -77,6 +77,12 @@ class YamlArrayModel < OpenStruct
77
77
  end
78
78
  end
79
79
 
80
+ class TypedModel < OpenStruct
81
+ def self._praxis_associations
82
+ {}
83
+ end
84
+ end
85
+
80
86
  # A set of resource classes for use in specs
81
87
  class BaseResource < Praxis::Mapper::Resource
82
88
  def href
@@ -100,6 +106,8 @@ class ParentResource < BaseResource
100
106
  end
101
107
 
102
108
  class SimpleResource < BaseResource
109
+ include Praxis::Mapper::Resources::Callbacks
110
+
103
111
  model SimpleModel
104
112
 
105
113
  resource_delegate other_model: [:other_attribute]
@@ -123,8 +131,132 @@ class SimpleResource < BaseResource
123
131
  property :no_deps, dependencies: []
124
132
 
125
133
  property :deep_nested_deps, dependencies: ['parent.simple_children.other_model.parent.display_name']
134
+
135
+ before(:update!, :do_before_update)
136
+ around(:update!, :do_around_update_nested)
137
+ around(:update!, :do_around_update)
138
+ # Define an after as a proc
139
+ after(:update!) do |number:|
140
+ _unused = number
141
+ record.after_count += 1
142
+ end
143
+
144
+ def do_before_update(number:)
145
+ _unused = number
146
+ record.before_count += 1
147
+ end
148
+
149
+ def do_around_update_nested(number:)
150
+ record.around_count += 100
151
+ yield(number: number)
152
+ end
153
+
154
+ def do_around_update(number:)
155
+ record.around_count += 50
156
+ yield(number: number)
157
+ end
158
+
159
+ around(:change_name, :do_around_change_name)
160
+ after(:change_name, :do_after_change_name)
161
+ # Define a before as a proc
162
+ before(:change_name) do |name, force:|
163
+ _unused = force
164
+ record.before_count += 1
165
+ record.name = name
166
+ record.force = false # Force always false in before
167
+ end
168
+
169
+ def do_after_change_name(name, force:)
170
+ _unused = force
171
+ record.after_count += 1
172
+ record.name += "-#{name}"
173
+ end
174
+
175
+ def do_around_change_name(name, force:)
176
+ record.around_count += 50
177
+
178
+ record.name += "-#{name}"
179
+ yield(name, force: force)
180
+ end
181
+
182
+ # Appends the name and overrides the force
183
+ def change_name(name, force:)
184
+ record.name += "-#{name}"
185
+ record.force = force
186
+ self
187
+ end
188
+
189
+ around(:argsonly, :do_around_argsonly)
190
+ def do_around_argsonly(name)
191
+ record.around_count += 50
192
+ record.name += name.to_s
193
+ yield(name)
194
+ end
195
+
196
+ def argsonly(name)
197
+ record.name += "-#{name}"
198
+ self
199
+ end
200
+
201
+ # Adds 1000 to the around count, plus whatever has been accumulated in before_count
202
+ def update!(number:)
203
+ record.around_count += number + record.before_count
204
+ self
205
+ end
126
206
  end
127
207
 
128
208
  class YamlArrayResource < BaseResource
129
209
  model YamlArrayModel
130
210
  end
211
+
212
+ class TypedResource < BaseResource
213
+ include Praxis::Mapper::Resources::TypedMethods
214
+
215
+ model TypedModel
216
+
217
+ signature(:update!) do
218
+ attribute :string_param, String, null: false
219
+ attribute :struct_param do
220
+ attribute :id, Integer
221
+ end
222
+ end
223
+ def update!(**payload)
224
+ payload
225
+ end
226
+
227
+ signature(:singlearg_update!) do
228
+ attribute :string_param, String, null: false
229
+ attribute :struct_param do
230
+ attribute :id, Integer
231
+ end
232
+ end
233
+ def singlearg_update!(payload)
234
+ payload
235
+ end
236
+
237
+ # Instance method: create, to make sure we support both an instance and a class method signature
238
+ signature(:create) do
239
+ attribute :id, String
240
+ end
241
+ def create(id:)
242
+ id
243
+ end
244
+
245
+ signature('self.create') do
246
+ attribute :name, String, regexp: /Praxis/
247
+ attribute :payload, TypedResource.signature(:update!), required: true
248
+ end
249
+
250
+ def self.create(**payload)
251
+ payload
252
+ end
253
+
254
+ signature('self.singlearg_create') do
255
+ attribute :name, String, regexp: /Praxis/
256
+ attribute :payload, TypedResource.signature(:update!), required: true
257
+ end
258
+
259
+ def self.singlearg_create(payload)
260
+ payload
261
+ end
262
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.pre.19
4
+ version: 2.0.pre.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
8
8
  - Dane Jensen
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-01-10 00:00:00.000000000 Z
12
+ date: 2022-05-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '6.0'
34
+ version: '6.2'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: '6.0'
41
+ version: '6.2'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: mime
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -359,7 +359,7 @@ dependencies:
359
359
  - - "~>"
360
360
  - !ruby/object:Gem::Version
361
361
  version: '5'
362
- description:
362
+ description:
363
363
  email:
364
364
  - blanquer@gmail.com
365
365
  - dane.jensen@gmail.com
@@ -463,6 +463,10 @@ files:
463
463
  - lib/praxis/handlers/xml_sample.rb
464
464
  - lib/praxis/mapper/active_model_compat.rb
465
465
  - lib/praxis/mapper/resource.rb
466
+ - lib/praxis/mapper/resources/callbacks.rb
467
+ - lib/praxis/mapper/resources/query_methods.rb
468
+ - lib/praxis/mapper/resources/query_proxy.rb
469
+ - lib/praxis/mapper/resources/typed_methods.rb
466
470
  - lib/praxis/mapper/selector_generator.rb
467
471
  - lib/praxis/mapper/sequel_compat.rb
468
472
  - lib/praxis/media_type.rb
@@ -543,6 +547,9 @@ files:
543
547
  - spec/praxis/file_group_spec.rb
544
548
  - spec/praxis/handlers/json_spec.rb
545
549
  - spec/praxis/mapper/resource_spec.rb
550
+ - spec/praxis/mapper/resources/callbacks_spec.rb
551
+ - spec/praxis/mapper/resources/query_proxy_spec.rb
552
+ - spec/praxis/mapper/resources/typed_methods_spec.rb
546
553
  - spec/praxis/mapper/selector_generator_spec.rb
547
554
  - spec/praxis/media_type_identifier_spec.rb
548
555
  - spec/praxis/media_type_spec.rb
@@ -660,7 +667,7 @@ homepage: https://github.com/praxis/praxis
660
667
  licenses:
661
668
  - MIT
662
669
  metadata: {}
663
- post_install_message:
670
+ post_install_message:
664
671
  rdoc_options: []
665
672
  require_paths:
666
673
  - lib
@@ -675,8 +682,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
675
682
  - !ruby/object:Gem::Version
676
683
  version: 1.3.1
677
684
  requirements: []
678
- rubygems_version: 3.1.2
679
- signing_key:
685
+ rubygems_version: 3.3.7
686
+ signing_key:
680
687
  specification_version: 4
681
688
  summary: Building APIs the way you want it.
682
689
  test_files: []