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

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.
@@ -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: []