praxis 2.0.pre.14 → 2.0.pre.15

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: 6af11de4fdf970e848d638ed6fd1f47db86dcd27562bca90e50805398a82b346
4
- data.tar.gz: dd4b664b1b8afde291e0402eeb0bb57178214ac1d8d6a4dc691f532fff7ae29f
3
+ metadata.gz: 9391f2cd6fb2c5a605a4b4c9f492371ef1d7f01849e94a9d17fdc51b95c211a9
4
+ data.tar.gz: 70c9f00cd12c7b3e99cebd10f86a98ac913433b8b96a41b658893b63ddc4b31b
5
5
  SHA512:
6
- metadata.gz: 525c64c37e1cfe27fe74c1ddbd02d3c6e45860acd0f8a79a5d9a0832274368c728dacc8096b3b37f63c302fe265161793320a64dcf44a84d99ee5074d9de9da2
7
- data.tar.gz: 79a70e175f17a14d98beddd66add981c03a8c720dd45855221b5b3223cb021d056381a5fd87880e3cbe2886755445182cf6cad3427de9f38b44220a26b23955d
6
+ metadata.gz: edfc42021b347aea6bd7b84853ebd901f55e4963612da2a6870e00b956b3352e51efd8cd3c360123625af48f89553bff6fa0ad7766e076bc164ba8ccf770ba7b
7
+ data.tar.gz: da2df887a849f03be390f7218f931807670a58addc8eb3d101b293e0827a522680ca1824f47bafcff2c3f26bfb45bd13e8ad0c6f02577edad12c5c8cfca60904
data/bin/praxis CHANGED
@@ -9,6 +9,12 @@ rescue Bundler::GemfileNotFound
9
9
  # no-op: we might be installed as a system gem
10
10
  end
11
11
 
12
+ if ARGV[0] == "version"
13
+ require 'praxis/version'
14
+ puts "Praxis version #{Praxis::VERSION}"
15
+ exit 0
16
+ end
17
+
12
18
  if ["routes","docs","console"].include? ARGV[0]
13
19
  require 'rake'
14
20
  require 'praxis'
@@ -97,8 +97,10 @@ module Praxis
97
97
  description( description || 'Standard response for successful HTTP requests.' )
98
98
 
99
99
  media_type media_type
100
- location location
101
- headers headers if headers
100
+ location if location
101
+ headers&.each do |(name, value)|
102
+ header(name: name, value: value)
103
+ end
102
104
  end
103
105
 
104
106
  api.response_template :created do |media_type: nil, location: nil, headers: nil, description: nil|
@@ -106,8 +108,10 @@ module Praxis
106
108
  description( description || 'The request has been fulfilled and resulted in a new resource being created.' )
107
109
 
108
110
  media_type media_type if media_type
109
- location location
110
- headers headers if headers
111
+ location if location
112
+ headers&.each do |(name, value)|
113
+ header(name: name, value: value)
114
+ end
111
115
  end
112
116
  end
113
117
 
@@ -32,5 +32,16 @@ module Praxis
32
32
  @member_type.domain_model
33
33
  end
34
34
 
35
+ def self.json_schema_type
36
+ :array
37
+ end
38
+
39
+ def self.as_json_schema(**args)
40
+ the_type = @attribute && @attribute.type || member_type
41
+ {
42
+ type: json_schema_type,
43
+ items: { '$ref': "#/components/schemas/#{the_type.id}" }
44
+ }
45
+ end
35
46
  end
36
47
  end
@@ -16,12 +16,27 @@ module Praxis
16
16
 
17
17
  def dump_response_headers_object( headers )
18
18
  headers.each_with_object({}) do |(name,data),accum|
19
- # data is a hash with :value and :type keys
20
- # How did we say in that must match a value in json schema again??
21
- accum[name] = {
22
- schema: SchemaObject.new(info: data[:type])
23
- # allowed values: [ data[:value] ] ??? is this the right json schema way?
24
- }
19
+ # each header comes from Praxis::ResponseDefinition
20
+ # the keys are the header names, and value can be:
21
+ # "true" => means it only needs to exist
22
+ # String => which means that it has to fully match
23
+ # Regex => which means it has to regexp match it
24
+
25
+ # Get the schema from the type (defaulting to string in case the type doesn't have the as_json_schema defined)
26
+ schema = data[:attribute].type.as_json_schema rescue { type: :string }
27
+ hash = { description: data[:description] || '', schema: schema }
28
+ # Note, our Headers in response definition are not full types...they're basically only
29
+ # strings, which can either match anything, match the exact word or match a regex
30
+ # they don't even have a description...
31
+ data_value = data[:value]
32
+ if data_value.is_a? String
33
+ hash[:pattern] = "^#{data_value}$" # Exact String match
34
+ elsif data_value.is_a? Regexp
35
+ sanitized_pattern = data_value.inspect[1..-2] #inspect returns enclosing '/' characters
36
+ hash[:pattern] = sanitized_pattern
37
+ end
38
+
39
+ accum[name] = hash
25
40
  end
26
41
  end
27
42
 
@@ -23,7 +23,8 @@ module Praxis
23
23
  end
24
24
 
25
25
  class ActiveRecordFilterQueryBuilder
26
- attr_reader :model, :filters_map
26
+ REFERENCES_STRING_SEPARATOR = '/'
27
+ attr_reader :model, :filters_map
27
28
 
28
29
  # Base query to build upon
29
30
  def initialize(query: , model:, filters_map:, debug: false)
@@ -65,8 +66,13 @@ module Praxis
65
66
  apply_single_condition = Proc.new do |condition, associated_query|
66
67
  colo = condition[:model].columns_hash[condition[:name].to_s]
67
68
  column_prefix = condition[:column_prefix]
68
- #Mark where clause referencing the appropriate alias
69
- associated_query = associated_query.references(build_reference_value(column_prefix, query: associated_query))
69
+
70
+ # Mark where clause referencing the appropriate alias IF it's not the root table, as there is no association to reference
71
+ # If we added root table as a reference, we better make sure it is not quoted, as it actually makes AR to see it as an
72
+ # unmatched reference and eager loads the whole association (it means eager load ALL the things). Not good.
73
+ unless for_model.table_name == column_prefix
74
+ associated_query = associated_query.references(build_reference_value(column_prefix, query: associated_query))
75
+ end
70
76
  self.class.add_clause(
71
77
  query: associated_query,
72
78
  column_prefix: column_prefix,
@@ -185,8 +191,7 @@ module Praxis
185
191
  h[name] = result[:associations_hash]
186
192
  conditions += result[:conditions]
187
193
  end
188
- column_prefix = nodetree.path == [ALIAS_TABLE_PREFIX] ? model.table_name : nodetree.path.join('/')
189
- #column_prefix = nodetree.path == [ALIAS_TABLE_PREFIX] ? nil : nodetree.path.join('/')
194
+ column_prefix = nodetree.path == [ALIAS_TABLE_PREFIX] ? model.table_name : nodetree.path.join(REFERENCES_STRING_SEPARATOR)
190
195
  nodetree.conditions.each do |condition|
191
196
  conditions += [condition.merge(column_prefix: column_prefix, model: model)]
192
197
  end
@@ -198,10 +198,20 @@ module Praxis
198
198
  obj = self.class.new
199
199
  obj.type = self.type
200
200
  obj.subtype = self.subtype
201
- obj.suffix = suffix || self.suffix || ''
201
+ target_suffix = suffix || self.suffix
202
+ obj.suffix = redundant_suffix(target_suffix) ? '' : target_suffix
202
203
  obj.parameters = self.parameters.merge(parameters)
203
204
 
204
205
  obj
205
206
  end
207
+
208
+ def redundant_suffix(suffix)
209
+ # application/json does not need to be suffixed with +json (same for application/xml)
210
+ # we're supporting text/json and text/xml for older formats as well
211
+ if (self.type == 'application' || self.type == 'text') && self.subtype == suffix
212
+ return true
213
+ end
214
+ false
215
+ end
206
216
  end
207
217
  end
@@ -55,52 +55,36 @@ module Praxis
55
55
  end
56
56
  end
57
57
 
58
- def location(loc=nil)
59
- return @spec[:location] if loc.nil?
60
- unless ( loc.is_a?(Regexp) || loc.is_a?(String) )
61
- raise Exceptions::InvalidConfiguration.new(
62
- "Invalid location specification. Location in response must be either a regular expression or a string."
63
- )
64
- end
65
- @spec[:location] = loc
66
- end
58
+ def location(loc=nil, description: nil)
59
+ return headers.dig('Location',:value) if loc.nil?
67
60
 
68
- def headers(hdrs = nil)
69
- return @spec[:headers] if hdrs.nil?
61
+ header('Location', loc, description: description)
62
+ end
70
63
 
71
- case hdrs
72
- when Array
73
- hdrs.each {|header_name| header(header_name) }
74
- when Hash
75
- header(hdrs)
76
- when String
77
- header(hdrs)
78
- else
79
- raise Exceptions::InvalidConfiguration.new(
80
- "Invalid headers specification: Arrays, Hash, or String must be used. Received: #{hdrs.inspect}"
81
- )
82
- end
64
+ def headers
65
+ @spec[:headers]
83
66
  end
84
67
 
85
- def header(hdr)
86
- case hdr
87
- when String
88
- @spec[:headers][hdr] = true
89
- when Hash
90
- hdr.each do | k, v |
91
- unless v.is_a?(Regexp) || v.is_a?(String)
92
- raise Exceptions::InvalidConfiguration.new(
93
- "Header definitions for #{k.inspect} can only match values of type String or Regexp. Received: #{v.inspect}"
94
- )
95
- end
96
- @spec[:headers][k] = v
97
- end
68
+ def header(name, value, description: nil)
69
+ the_type, args = case value
70
+ when nil,String
71
+ [String, {}]
72
+ when Regexp
73
+ # A regexp means it's gonna be a String typed, attached to a regexp
74
+ [String, { regexp: value }]
98
75
  else
99
76
  raise Exceptions::InvalidConfiguration.new(
100
- "A header definition can only take a String (to match the name) or" +
101
- " a Hash (to match both the name and the value). Received: #{hdr.inspect}"
77
+ "A header definition for a response can only take String, Regexp or nil values (to match anything)." +
78
+ "Received the following value for header name #{name}: #{value}"
102
79
  )
103
80
  end
81
+
82
+ info = {
83
+ value: value,
84
+ attribute: Attributor::Attribute.new(the_type, **args)
85
+ }
86
+ info[:description] = description if description
87
+ @spec[:headers][name] = info
104
88
  end
105
89
 
106
90
  def example(context=nil)
@@ -123,13 +107,14 @@ module Praxis
123
107
  :status => status,
124
108
  :headers => {}
125
109
  }
126
- content[:location] = _describe_header(location) unless location == nil
127
110
 
128
111
  unless headers == nil
129
112
  headers.each do |name, value|
130
113
  content[:headers][name] = _describe_header(value)
131
114
  end
132
115
  end
116
+ content[:location] = content[:headers]['Location']
117
+
133
118
 
134
119
  if self.media_type
135
120
  payload = media_type.describe(true)
@@ -173,14 +158,14 @@ module Praxis
173
158
  end
174
159
 
175
160
  def _describe_header(data)
176
- data_type = data.is_a?(Regexp) ? :regexp : :string
177
- data_value = data.is_a?(Regexp) ? data.inspect : data
161
+
162
+ data_type = data[:value].is_a?(Regexp) ? :regexp : :string
163
+ data_value = data[:value].is_a?(Regexp) ? data[:value].inspect : data[:value]
178
164
  { :value => data_value, :type => data_type }
179
165
  end
180
166
 
181
167
  def validate(response, validate_body: false)
182
168
  validate_status!(response)
183
- validate_location!(response)
184
169
  validate_headers!(response)
185
170
  validate_content_type!(response)
186
171
  validate_parts!(response)
@@ -222,23 +207,13 @@ module Praxis
222
207
  end
223
208
  end
224
209
 
225
-
226
- # Validates 'Location' header
227
- #
228
- # @raise [Exceptions::Validation] When location header does not match to the defined one.
229
- #
230
- def validate_location!(response)
231
- return if location.nil? || location === response.headers['Location']
232
- raise Exceptions::Validation.new("LOCATION does not match #{location.inspect}")
233
- end
234
-
235
-
236
210
  # Validates Headers
237
211
  #
238
212
  # @raise [Exceptions::Validation] When there is a missing required header..
239
213
  #
240
214
  def validate_headers!(response)
241
215
  return unless headers
216
+
242
217
  headers.each do |name, value|
243
218
  if name.is_a? Symbol
244
219
  raise Exceptions::Validation.new(
@@ -252,20 +227,25 @@ module Praxis
252
227
  )
253
228
  end
254
229
 
255
- case value
256
- when String
257
- if response.headers[name] != value
258
- raise Exceptions::Validation.new(
259
- "Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name]}."
260
- )
261
- end
262
- when Regexp
263
- if response.headers[name] !~ value
264
- raise Exceptions::Validation.new(
265
- "Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name].inspect}."
266
- )
267
- end
230
+ errors = value[:attribute].validate(response.headers[name])
231
+
232
+ unless errors.empty?
233
+ raise Exceptions::Validation.new("Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name]}.")
268
234
  end
235
+ # case value
236
+ # when String
237
+ # if response.headers[name] != value
238
+ # raise Exceptions::Validation.new(
239
+ # "Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name]}."
240
+ # )
241
+ # end
242
+ # when Regexp
243
+ # if response.headers[name] !~ value
244
+ # raise Exceptions::Validation.new(
245
+ # "Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name].inspect}."
246
+ # )
247
+ # end
248
+ # end
269
249
  end
270
250
  end
271
251
 
@@ -160,7 +160,9 @@ module Praxis
160
160
 
161
161
  media_type media_type if media_type
162
162
  location location if location
163
- headers headers if headers
163
+ headers&.each do |(name, value)|
164
+ header(name: name, value: value)
165
+ end
164
166
  end
165
167
  end
166
168
 
@@ -7,14 +7,14 @@ namespace :praxis do
7
7
  table = Terminal::Table.new title: "Routes",
8
8
  headings: [
9
9
  "Version", "Path", "Verb",
10
- "Resource", "Action", "Implementation", "Options"
10
+ "Endpoint", "Action", "Implementation", "Options"
11
11
  ]
12
12
 
13
13
  rows = []
14
- Praxis::Application.instance.endpoint_definitions.each do |resource_definition|
15
- resource_definition.actions.each do |name, action|
14
+ Praxis::Application.instance.endpoint_definitions.each do |endpoint_definition|
15
+ endpoint_definition.actions.each do |name, action|
16
16
  method = begin
17
- m = resource_definition.controller.instance_method(name)
17
+ m = endpoint_definition.controller.instance_method(name)
18
18
  rescue
19
19
  nil
20
20
  end
@@ -22,13 +22,13 @@ namespace :praxis do
22
22
  method_name = method ? "#{method.owner.name}##{method.name}" : 'n/a'
23
23
 
24
24
  row = {
25
- resource: resource_definition.name,
25
+ resource: endpoint_definition.name,
26
26
  action: name,
27
27
  implementation: method_name,
28
28
  }
29
29
 
30
30
  unless action.route
31
- warn "Warning: No routes defined for #{resource_definition.name}##{name}."
31
+ warn "Warning: No routes defined for #{endpoint_definition.name}##{name}."
32
32
  rows << row
33
33
  else
34
34
  route = action.route
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '2.0.pre.14'
2
+ VERSION = '2.0.pre.15'
3
3
  end
@@ -40,7 +40,9 @@ describe Praxis::ActionDefinition do
40
40
 
41
41
  media_type media_type
42
42
  location location
43
- headers headers if headers
43
+ headers&.each do |(name, value)|
44
+ header(name, value)
45
+ end
44
46
  end
45
47
  end
46
48
  Praxis::ActionDefinition.new(:foo, endpoint_definition) do
@@ -218,7 +218,21 @@ describe Praxis::MediaTypeIdentifier do
218
218
 
219
219
  it 'replaces suffix and parameters and adds new ones' do
220
220
  expect(complex_subject + 'json; nuts=false; cherry=true').to \
221
- eq(described_class.new('application/vnd.icecream+json; cherry=true; nuts=false'))
221
+ eq(described_class.new('application/vnd.icecream+json; cherry=true; nuts=false'))
222
+ end
223
+
224
+ context 'does not add json for an already json identifier' do
225
+ it 'non-parameterized mediatypes simply ignore adding the suffix' do
226
+ plain_application_json = described_class.new('application/json')
227
+
228
+ expect(plain_application_json + 'json').to \
229
+ eq(plain_application_json)
230
+ end
231
+ it 'parameterized mediatypes still keeps them' do
232
+ parameterized_application_json = described_class.new('application/json; cherry=true; nuts=false')
233
+ expect(parameterized_application_json + 'json').to \
234
+ eq(parameterized_application_json)
235
+ end
222
236
  end
223
237
  end
224
238
  end
@@ -8,7 +8,8 @@ describe Praxis::ResponseDefinition do
8
8
  Proc.new do
9
9
  status 200
10
10
  description 'test description'
11
- headers({ "X-Header" => "value", "Content-Type" => "application/some-type" })
11
+ header( "X-Header", "value", description: 'Very nais header')
12
+ header( "Content-Type", "application/some-type" )
12
13
  end
13
14
  end
14
15
 
@@ -17,7 +18,7 @@ describe Praxis::ResponseDefinition do
17
18
  its(:parts) { should be(nil) }
18
19
  let(:response_status) { 200 }
19
20
  let(:response_content_type) { "application/some-type" }
20
- let(:response_headers) { { "X-Header" => "value", "Content-Type" => response_content_type} }
21
+ let(:response_headers) { { "X-Header" => "value", "Content-Type" => response_content_type, "Location" => '/somewhere/over/the/rainbow'} }
21
22
 
22
23
  let(:response) { instance_double("Praxis::Response", status: response_status , headers: response_headers, content_type: response_content_type ) }
23
24
 
@@ -105,29 +106,6 @@ describe Praxis::ResponseDefinition do
105
106
  end
106
107
  end
107
108
 
108
- context '#headers' do
109
- it 'accepts a Hash' do
110
- response_definition.headers Hash["X-Header" => "value", "Content-Type" => "application/some-type"]
111
- expect(response_definition.headers).to be_a(Hash)
112
- end
113
-
114
- it 'accepts an Array' do
115
- response_definition.headers ["X-Header: value", "Content-Type: application/some-type"]
116
- expect(response_definition.headers).to be_a(Hash)
117
- expect(response_definition.headers.keys).to include("X-Header: value", "Content-Type: application/some-type")
118
- end
119
-
120
- it 'accepts a String' do
121
- response_definition.headers "X-Header: value"
122
- expect(response_definition.headers).to be_a(Hash)
123
- expect(response_definition.headers.keys).to include("X-Header: value")
124
- end
125
-
126
- it 'should return an error when headers are not a Hash, Array or String object' do
127
- expect{ response_definition.headers Object.new }. to raise_error(Praxis::Exceptions::InvalidConfiguration)
128
- end
129
- end
130
-
131
109
  context '#parts' do
132
110
  context 'with a :like argument (and no block)' do
133
111
  before do
@@ -242,7 +220,6 @@ describe Praxis::ResponseDefinition do
242
220
 
243
221
  it "calls all the validation sub-functions" do
244
222
  expect(response_definition).to receive(:validate_status!).once
245
- expect(response_definition).to receive(:validate_location!).once
246
223
  expect(response_definition).to receive(:validate_headers!).once
247
224
  expect(response_definition).to receive(:validate_content_type!).once
248
225
  response_definition.validate(response)
@@ -272,112 +249,39 @@ describe Praxis::ResponseDefinition do
272
249
 
273
250
  end
274
251
 
275
- describe "#validate_location!" do
276
- let(:block) { proc { status 200 } }
277
-
278
- context 'checking location mismatches' do
279
- before { response_definition.location(location) }
280
-
281
- context 'for Regexp' do
282
- let(:location) { /no_match/ }
283
-
284
- it 'should raise an error' do
285
- expect {
286
- response_definition.validate_location!(response)
287
- }.to raise_error(Praxis::Exceptions::Validation)
288
- end
289
- end
290
-
291
- context 'for String' do
292
- let(:location) { "no_match" }
293
- it 'should raise error' do
294
- expect {
295
- response_definition.validate_location!(response)
296
- }.to raise_error(Praxis::Exceptions::Validation)
297
- end
252
+ describe "#validate_headers!" do
253
+ context 'when there are missing headers' do
254
+ it 'should raise error' do
255
+ response_definition.header('X-Unknown', 'test')
256
+ expect {
257
+ response_definition.validate_headers!(response)
258
+ }.to raise_error(Praxis::Exceptions::Validation)
298
259
  end
299
-
300
260
  end
301
- end
302
-
303
- describe "#validate_headers!" do
304
- before { response_definition.headers(headers) }
305
- context 'checking headers are set' do
306
- context 'when there are missing headers' do
307
- let (:headers) { { 'X-some' => 'test' } }
308
- it 'should raise error' do
309
- expect {
310
- response_definition.validate_headers!(response)
311
- }.to raise_error(Praxis::Exceptions::Validation)
312
- end
261
+ context 'when headers with same names are returned' do
262
+ it 'a simply required header should not raise error just by being there' do
263
+ response_definition.header('X-Header', nil)
264
+ expect {
265
+ response_definition.validate_headers!(response)
266
+ }.to_not raise_error
313
267
  end
314
-
315
- context "when headers specs are name strings" do
316
- context "and is missing" do
317
- let (:headers) { [ "X-Just-Key" ] }
318
- it 'should raise error' do
319
- expect {
320
- response_definition.validate_headers!(response)
321
- }.to raise_error(Praxis::Exceptions::Validation)
322
- end
323
- end
324
-
325
- context "and is not missing" do
326
- let (:headers) { [ "X-Header" ] }
327
- it 'should not raise error' do
328
- expect {
329
- response_definition.validate_headers!(response)
330
- }.not_to raise_error
331
- end
332
- end
268
+ it 'an exact string header should not raise error if it fully matches' do
269
+ response_definition.header('X-Header', 'value')
270
+ expect {
271
+ response_definition.validate_headers!(response)
272
+ }.to_not raise_error
333
273
  end
334
-
335
- context "when header specs are hashes" do
336
- context "and is missing" do
337
- let (:headers) {
338
- [ { "X-Just-Key" => "notfoodbar" } ]
339
- }
340
- it 'should raise error' do
341
- expect {
342
- response_definition.validate_headers!(response)
343
- }.to raise_error(Praxis::Exceptions::Validation)
344
- end
345
- end
346
-
347
- context "and is not missing" do
348
- let (:headers) {
349
- [ { "X-Header" => "value" } ]
350
- }
351
- it 'should not raise error' do
352
- expect {
353
- response_definition.validate_headers!(response)
354
- }.not_to raise_error
355
- end
356
- end
274
+ it 'a regexp header should not raise error if it matches the regexp' do
275
+ response_definition.header('X-Header', /value/)
276
+ expect {
277
+ response_definition.validate_headers!(response)
278
+ }.to_not raise_error
357
279
  end
358
-
359
- context "when header specs are of mixed type " do
360
- context "and is missing" do
361
- let (:headers) {
362
- [ { "X-Header" => "value" }, "not-gonna-find-me" ]
363
- }
364
- it 'should raise error' do
365
- expect {
366
- response_definition.validate_headers!(response)
367
- }.to raise_error(Praxis::Exceptions::Validation)
368
- end
369
- end
370
-
371
- context "and is not missing" do
372
- let (:headers) {
373
- [ { "X-Header" => "value" }, "Content-Type" ]
374
- }
375
- it 'should not raise error' do
376
- expect {
377
- response_definition.validate_headers!(response)
378
- }.not_to raise_error
379
- end
380
- end
280
+ it 'a regexp header should raise error if it does not match the regexp' do
281
+ response_definition.header('X-Header', /anotherthing/)
282
+ expect {
283
+ response_definition.validate_headers!(response)
284
+ }.to raise_error(Praxis::Exceptions::Validation)
381
285
  end
382
286
  end
383
287
  end
@@ -478,7 +382,10 @@ describe Praxis::ResponseDefinition do
478
382
  if parts || parts_block
479
383
  parts ? response.parts(nil, **parts, &parts_block) : response.parts(nil, &parts_block)
480
384
  end
481
- response.headers(headers) if headers
385
+
386
+ headers&.each do |(name, value)|
387
+ response.header(name, value)
388
+ end
482
389
  end
483
390
 
484
391
  context 'for a definition with a media type' do
@@ -520,8 +427,9 @@ describe Praxis::ResponseDefinition do
520
427
  its([:location]){ should == {value: location.inspect ,type: :regexp} }
521
428
 
522
429
  it 'should have a header defined with value and type keys' do
523
- expect( output[:headers] ).to have(1).keys
430
+ expect( output[:headers] ).to have(2).keys
524
431
  expect( output[:headers]['Header1'] ).to eq({value: 'Value1' ,type: :string })
432
+ expect( output[:headers]['Location'] ).to eq({value: "/\\/my\\/url\\//" ,type: :regexp })
525
433
  end
526
434
  end
527
435
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module V1
4
+ module Resources
5
+ module Concerns
6
+ module Href
7
+ extend ActiveSupport::Concern
8
+
9
+ # Base module where the href concern will grab constants from
10
+ included do
11
+ def self.base_module
12
+ ::V1
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def endpoint_path_template
18
+ # memoize a templated path for an endpoint, like
19
+ # /im/contacts/%{id}
20
+ return @endpoint_path_template if @endpoint_path_template
21
+
22
+ path = self.base_module.const_get(:Endpoints).const_get(model.name.split(':').last.pluralize).canonical_path.route.path
23
+ @endpoint_path_template = path.names.inject(path.to_s) { |p, name| p.sub(':' + name, "%{#{name}}") }
24
+ end
25
+ end
26
+
27
+ def href
28
+ format(self.class.endpoint_path_template, id: id)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../concerns/href'
4
+
3
5
  module V1
4
6
  module Resources
5
7
  class Base < Praxis::Mapper::Resource
8
+ include Resources::Concerns::Href
9
+
6
10
  # Base for all V1 resources.
7
11
  # Resources withing a single version should have resource mappings separate from other versions
8
12
  # and the Mapper::Resource will appropriately maintain different model_maps for each Base classes
@@ -4,7 +4,7 @@ Praxis::Application.configure do |application|
4
4
  # Configure the Mapper plugin (if we want to use all the filtering/field_selection extensions)
5
5
  application.bootloader.use Praxis::Plugins::MapperPlugin
6
6
  # Configure the Pagination plugin (if we want to use all the pagination/ordering extensions)
7
- application.bootloader.use Praxis::Plugins::PaginationPlugin, {
7
+ application.bootloader.use Praxis::Plugins::PaginationPlugin, **{
8
8
  # max_items: 500, # Unlimited by default,
9
9
  # default_page_size: 100,
10
10
  # paging_default_mode: {by: :id},
@@ -18,7 +18,7 @@ module <%= version_module %>
18
18
  <%- if action_enabled?(:create) -%>
19
19
  def self.create(payload)
20
20
  # Assuming the API field names directly map the the model attributes. Massage if appropriate.
21
- self.new(model.create(*payload.to_h))
21
+ self.new(model.create(**payload.to_h))
22
22
  end
23
23
  <%- end -%>
24
24
 
@@ -27,7 +27,7 @@ module <%= version_module %>
27
27
  record = model.find_by(id: id)
28
28
  return nil unless record
29
29
  # Assuming the API field names directly map the the model attributes. Massage if appropriate.
30
- record.update(*payload.to_h)
30
+ record.update(**payload.to_h)
31
31
  self.new(record)
32
32
  end
33
33
  <%- 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.14
4
+ version: 2.0.pre.15
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: 2021-02-26 00:00:00.000000000 Z
12
+ date: 2021-04-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -367,7 +367,7 @@ dependencies:
367
367
  - - ">"
368
368
  - !ruby/object:Gem::Version
369
369
  version: '4'
370
- description:
370
+ description:
371
371
  email:
372
372
  - blanquer@gmail.com
373
373
  - dane.jensen@gmail.com
@@ -647,6 +647,7 @@ files:
647
647
  - tasks/thor/templates/generator/example_app/Rakefile
648
648
  - tasks/thor/templates/generator/example_app/app/models/user.rb
649
649
  - tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb
650
+ - tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb
650
651
  - tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb
651
652
  - tasks/thor/templates/generator/example_app/app/v1/resources/base.rb
652
653
  - tasks/thor/templates/generator/example_app/app/v1/resources/user.rb
@@ -671,7 +672,7 @@ homepage: https://github.com/praxis/praxis
671
672
  licenses:
672
673
  - MIT
673
674
  metadata: {}
674
- post_install_message:
675
+ post_install_message:
675
676
  rdoc_options: []
676
677
  require_paths:
677
678
  - lib
@@ -687,7 +688,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
687
688
  version: 1.3.1
688
689
  requirements: []
689
690
  rubygems_version: 3.1.2
690
- signing_key:
691
+ signing_key:
691
692
  specification_version: 4
692
693
  summary: Building APIs the way you want it.
693
694
  test_files: []