praxis 2.0.pre.33 → 2.0.pre.34
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +2 -0
- data/.gitignore +0 -5
- data/.travis.yml +2 -3
- data/Appraisals +6 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +6 -6
- data/gemfiles/active_6.gemfile +11 -9
- data/gemfiles/active_6.gemfile.lock +21 -15
- data/gemfiles/active_7.gemfile +11 -9
- data/gemfiles/active_7.gemfile.lock +21 -15
- data/lib/praxis/application.rb +3 -0
- data/lib/praxis/blueprint.rb +2 -1
- data/lib/praxis/docs/open_api/paths_object.rb +1 -1
- data/lib/praxis/docs/open_api/schema_object.rb +48 -27
- data/lib/praxis/docs/open_api_generator.rb +6 -6
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +0 -1
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +1 -1
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +5 -0
- data/lib/praxis/extensions/field_expansion.rb +7 -4
- data/lib/praxis/extensions/pagination/ordering_params.rb +0 -4
- data/lib/praxis/field_expander.rb +18 -3
- data/lib/praxis/handlers/xml_sample.rb +1 -1
- data/lib/praxis/mapper/resource.rb +1 -1
- data/lib/praxis/mapper/selector_generator.rb +1 -1
- data/lib/praxis/multipart/parser.rb +1 -1
- data/lib/praxis/types/splattable_string_array.rb +13 -0
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +1 -1
- data/praxis.gemspec +3 -3
- data/spec/functional_library_spec.rb +91 -28
- data/spec/praxis/application_spec.rb +7 -2
- data/spec/praxis/blueprint_spec.rb +53 -55
- data/spec/praxis/controller_spec.rb +4 -1
- data/spec/praxis/extensions/field_expansion_spec.rb +2 -2
- data/spec/praxis/extensions/pagination/ordering_params_spec.rb +0 -2
- data/spec/praxis/field_expander_spec.rb +46 -0
- data/spec/spec_app/app/controllers/base_class.rb +12 -0
- data/spec/spec_app/app/resources/book.rb +9 -0
- data/spec/spec_app/config.ru +0 -1
- data/spec/spec_app/design/media_types/book.rb +2 -0
- data/spec/spec_app/design/resources/authors.rb +3 -7
- data/spec/support/spec_blueprints.rb +15 -0
- data/tasks/thor/templates/generator/example_app/Gemfile +1 -1
- data/tasks/thor/templates/generator/example_app/app/models/user.rb +0 -1
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +0 -1
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb +10 -4
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +24 -0
- data/tasks/thor/templates/generator/example_app/design/api.rb +12 -1
- data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +1 -1
- data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +1 -1
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -1
- metadata +18 -17
- data/.simplecov +0 -9
@@ -93,7 +93,7 @@ module Praxis
|
|
93
93
|
# The `as:` can be used for properties that correspond to an underlying association of a different name. With this, the selector generator, is able to
|
94
94
|
# follow and pass any incoming nested fields when necessary (as opposed to only add dependencies and discard nested fields)
|
95
95
|
# No dependencies are allowed to be defined if `as:` is used (as the dependencies should be defined at the final aliased property)
|
96
|
-
def self.property(name, dependencies: nil, as: nil)
|
96
|
+
def self.property(name, dependencies: nil, as: nil)
|
97
97
|
raise "Error defining property '#{name}' in #{self}. Property names must be symbols, not strings." unless name.is_a? Symbol
|
98
98
|
|
99
99
|
h = { dependencies: dependencies }
|
@@ -168,7 +168,7 @@ module Praxis
|
|
168
168
|
# Save the read body part.
|
169
169
|
body << @buf.slice!(0, @buf.size - (@boundary_size + 4)) if head && (@boundary_size + 4 < @buf.size)
|
170
170
|
|
171
|
-
content = @io.read(@content_length &&
|
171
|
+
content = @io.read(@content_length && @content_length <= BUFSIZE ? @content_length : BUFSIZE)
|
172
172
|
raise EOFError, 'bad content body' if content.nil? || content.empty?
|
173
173
|
|
174
174
|
@buf << content
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Praxis
|
4
|
+
module Types
|
5
|
+
class SplattableStringArray < Attributor::Collection
|
6
|
+
# Make a type, to allow to load the value, as s single string, if it isn't a numerable
|
7
|
+
# This way we can do displayable: 'foobar' , or displayable: ['one', 'two']
|
8
|
+
def self.decode_string(value, _context)
|
9
|
+
Array(value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/praxis/version.rb
CHANGED
data/lib/praxis.rb
CHANGED
@@ -52,7 +52,6 @@ module Praxis
|
|
52
52
|
autoload :BlueprintAttributeGroup, 'praxis/blueprint_attribute_group'
|
53
53
|
autoload :FieldExpander, 'praxis/field_expander'
|
54
54
|
autoload :Renderer, 'praxis/renderer'
|
55
|
-
|
56
55
|
autoload :Notifications, 'praxis/notifications'
|
57
56
|
autoload :MiddlewareApp, 'praxis/middleware_app'
|
58
57
|
|
@@ -67,6 +66,7 @@ module Praxis
|
|
67
66
|
autoload :FuzzyHash, 'praxis/types/fuzzy_hash'
|
68
67
|
autoload :MediaTypeCommon, 'praxis/types/media_type_common'
|
69
68
|
autoload :MultipartArray, 'praxis/types/multipart_array'
|
69
|
+
autoload :SplattableStringArray, 'praxis/types/splattable_string_array'
|
70
70
|
end
|
71
71
|
|
72
72
|
autoload :MediaType, 'praxis/media_type'
|
data/praxis.gemspec
CHANGED
@@ -23,16 +23,16 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables << 'praxis'
|
24
24
|
|
25
25
|
spec.add_dependency 'activesupport', '>= 3'
|
26
|
-
spec.add_dependency 'attributor', '>= 7.
|
26
|
+
spec.add_dependency 'attributor', '>= 7.1'
|
27
27
|
spec.add_dependency 'mime', '~> 0'
|
28
28
|
spec.add_dependency 'mustermann', '>=1.1'
|
29
29
|
spec.add_dependency 'rack', '>= 1'
|
30
30
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
31
31
|
spec.add_dependency 'thor'
|
32
32
|
|
33
|
+
spec.add_development_dependency 'appraisal'
|
33
34
|
spec.add_development_dependency 'bundler'
|
34
35
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
35
|
-
spec.add_development_dependency "appraisal"
|
36
36
|
|
37
37
|
if RUBY_PLATFORM !~ /java/
|
38
38
|
spec.add_development_dependency 'pry'
|
@@ -42,7 +42,7 @@ Gem::Specification.new do |spec|
|
|
42
42
|
else
|
43
43
|
spec.add_development_dependency 'jdbc-sqlite3'
|
44
44
|
end
|
45
|
-
spec.add_development_dependency '
|
45
|
+
spec.add_development_dependency 'coveralls_reborn', '~> 0.27.0'
|
46
46
|
spec.add_development_dependency 'fuubar', '~> 2'
|
47
47
|
spec.add_development_dependency 'guard', '~> 2'
|
48
48
|
spec.add_development_dependency 'guard-bundler', '~> 2'
|
@@ -132,6 +132,38 @@ describe 'Functional specs for books with connected DB' do
|
|
132
132
|
end
|
133
133
|
end
|
134
134
|
end
|
135
|
+
|
136
|
+
context 'with protected attributes' do
|
137
|
+
let(:fields_q) { 'id,special,multi' }
|
138
|
+
before do
|
139
|
+
expect(::Book.attributes[:special].options[:displayable]).to eq(['special#read'])
|
140
|
+
expect(::Book.attributes[:multi].options[:displayable]).to eq(['special#read', 'normal#read'])
|
141
|
+
end
|
142
|
+
context 'using the API' do
|
143
|
+
context 'when the user has the privilege for an attribute' do
|
144
|
+
before { allow(::BaseClass).to receive(:current_user_privs).and_return(['special#read']) }
|
145
|
+
it 'renders it' do
|
146
|
+
expect(parsed_response.keys).to include(:special)
|
147
|
+
expect(parsed_response.keys).to_not include(:multi)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'when the user does not have the privilege' do
|
152
|
+
before { allow(::BaseClass).to receive(:current_user_privs).and_return(['anotherpriv!']) }
|
153
|
+
it 'does not render it' do
|
154
|
+
expect(parsed_response.keys).to_not include(:special)
|
155
|
+
expect(parsed_response.keys).to_not include(:multi)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'when the user has multiple privilege for multiple attributes' do
|
160
|
+
before { allow(::BaseClass).to receive(:current_user_privs).and_return(['special#read', 'normal#read']) }
|
161
|
+
it 'renders both' do
|
162
|
+
expect(parsed_response.keys).to include(:special, :multi)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
135
167
|
end
|
136
168
|
end
|
137
169
|
|
@@ -139,8 +171,9 @@ describe 'Functional specs for books with connected DB' do
|
|
139
171
|
let(:filters_q) { '' }
|
140
172
|
let(:fields_q) { '' }
|
141
173
|
let(:order_q) { '' }
|
174
|
+
let(:pagination_q) { '' }
|
142
175
|
subject do
|
143
|
-
get '/api/authors', api_version: '1.0', fields: fields_q, filters: filters_q, order: order_q
|
176
|
+
get '/api/authors', api_version: '1.0', fields: fields_q, filters: filters_q, order: order_q, pagination: pagination_q
|
144
177
|
end
|
145
178
|
|
146
179
|
context 'all authors' do
|
@@ -148,37 +181,67 @@ describe 'Functional specs for books with connected DB' do
|
|
148
181
|
let(:base_query) do
|
149
182
|
ActiveAuthor.joins(books: :author).where('active_books.simple_name LIKE ?', 'book%').where('authors_active_books.id > ?', 0)
|
150
183
|
end
|
151
|
-
|
152
|
-
|
153
|
-
|
184
|
+
context 'using any filters/order/pagination attributes works, since they have been defined empty in the block definition' do
|
185
|
+
it 'is successful' do
|
186
|
+
expect(subject).to be_successful
|
187
|
+
expect(subject.headers['Content-Type']).to eq('application/vnd.acme.author; type=collection')
|
154
188
|
|
155
|
-
|
156
|
-
end
|
157
|
-
context 'ordering' do
|
158
|
-
context 'using direct attributes' do
|
159
|
-
let(:order_q) { '-name,id' }
|
160
|
-
it { expect(subject).to be_successful }
|
189
|
+
expect(parsed_response.size).to eq base_query.count
|
161
190
|
end
|
162
|
-
context '
|
163
|
-
|
164
|
-
|
165
|
-
expect(subject).to be_successful
|
166
|
-
|
167
|
-
|
168
|
-
|
191
|
+
context 'ordering' do
|
192
|
+
context 'using direct attributes' do
|
193
|
+
let(:order_q) { '-name,id' }
|
194
|
+
it { expect(subject).to be_successful }
|
195
|
+
end
|
196
|
+
context 'using nested attributes' do
|
197
|
+
let(:order_q) { '-name,books.name' }
|
198
|
+
it 'is successful' do
|
199
|
+
expect(subject).to be_successful
|
200
|
+
ids = base_query.order('active_authors.name DESC', 'active_books.simple_name DESC').pluck(:id)
|
201
|
+
|
202
|
+
expect(parsed_response.map { |book| book[:id] }).to eq ids
|
203
|
+
end
|
169
204
|
end
|
170
205
|
end
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
206
|
+
context 'filtering and sorting' do
|
207
|
+
context 'using the same tables, including the base query one' do
|
208
|
+
let(:order_q) { '-name,books.author.name' }
|
209
|
+
let(:filters_q) { 'books.name!' }
|
210
|
+
let(:fields_q) { 'id,books{name}' }
|
211
|
+
it 'is successful' do
|
212
|
+
expect(subject).to be_successful
|
213
|
+
ids = base_query.order('active_authors.name DESC', 'active_books.simple_name DESC').pluck(:id)
|
214
|
+
|
215
|
+
expect(parsed_response.map { |book| book[:id] }).to eq ids
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
context 'with page-based pagination' do
|
220
|
+
context 'using the same tables, including the base query one' do
|
221
|
+
let(:order_q) { '-name,books.author.name' }
|
222
|
+
let(:filters_q) { 'books.name!' }
|
223
|
+
let(:fields_q) { 'id,books{name}' }
|
224
|
+
let(:pagination_q) { 'page=1,items=2' }
|
225
|
+
it 'is successful' do
|
226
|
+
expect(subject).to be_successful
|
227
|
+
ids = base_query.order('active_authors.name DESC', 'active_books.simple_name DESC').limit(2).pluck(:id)
|
228
|
+
|
229
|
+
expect(parsed_response.map { |book| book[:id] }).to eq ids
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
context 'with cursor-based pagination' do
|
234
|
+
context 'using the same tables, including the base query one' do
|
235
|
+
let(:order_q) { '-name,books.author.name' }
|
236
|
+
let(:filters_q) { 'books.name!' }
|
237
|
+
let(:fields_q) { 'id,books{name}' }
|
238
|
+
let(:pagination_q) { 'by=name,items=2' }
|
239
|
+
it 'is successful' do
|
240
|
+
expect(subject).to be_successful
|
241
|
+
ids = base_query.order('active_authors.name DESC', 'active_books.simple_name DESC').limit(2).pluck(:id)
|
242
|
+
|
243
|
+
expect(parsed_response.map { |book| book[:id] }).to eq ids
|
244
|
+
end
|
182
245
|
end
|
183
246
|
end
|
184
247
|
end
|
@@ -98,11 +98,16 @@ describe Praxis::Application do
|
|
98
98
|
end
|
99
99
|
|
100
100
|
describe '#inspect' do
|
101
|
-
let(:klass)
|
101
|
+
let(:klass) do
|
102
|
+
Class.new(Praxis::Application) do
|
103
|
+
def self.to_s
|
104
|
+
'SomeApplication'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
102
108
|
subject { klass.instance }
|
103
109
|
|
104
110
|
it 'includes name, object ID and root' do
|
105
|
-
SomeApplication = klass # de-anonymize class name
|
106
111
|
klass.instance.instance_variable_set(:@root, '/tmp')
|
107
112
|
expect(subject.inspect).to match(%r{#<SomeApplication#[0-9]+ @root=/tmp>})
|
108
113
|
end
|
@@ -21,7 +21,7 @@ describe Praxis::Blueprint do
|
|
21
21
|
# - Fail. Cannot determine type
|
22
22
|
# 1.2) if it has a block
|
23
23
|
# 1.2.1) with an reference with an attr with the same name
|
24
|
-
# - We assume you're re/defining a new Struct (or Struct[]), and we will incorporate the reference type
|
24
|
+
# - We assume you're re/defining a new Struct (or Struct[]), and we will incorporate the reference type
|
25
25
|
# for the matching name in case you are indeed redefining a subset of the attributes, so you can enjoy inheritance
|
26
26
|
# 1.2.2) without a ref (or the ref does not have same attribute name)
|
27
27
|
# - defaulted to Struct (if you meant Collection.of(Struct) things would fail later somehow)
|
@@ -33,20 +33,20 @@ describe Praxis::Blueprint do
|
|
33
33
|
# 2.2) if it has a block
|
34
34
|
# - Same as above: use type and options provided, ignore ref if there is one (with or without matching attribute name).
|
35
35
|
|
36
|
-
let(:mytype) do
|
36
|
+
let(:mytype) do
|
37
37
|
Praxis::Blueprint.finalize!
|
38
|
-
Class.new(Praxis::Blueprint, &myblock).tap
|
38
|
+
Class.new(Praxis::Blueprint, &myblock).tap(&:_finalize!)
|
39
39
|
end
|
40
40
|
context 'with no explicit type specified' do
|
41
41
|
context 'without a block (if it is a leaf)' do
|
42
42
|
context 'that has a reference with an attribute with the same name' do
|
43
|
-
let(:myblock)
|
44
|
-
|
43
|
+
let(:myblock) do
|
44
|
+
proc do
|
45
45
|
attributes reference: PersonBlueprint do
|
46
46
|
attribute :age, required: true, min: 42
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
49
|
+
end
|
50
50
|
it 'uses type from reference' do
|
51
51
|
expect(mytype.attributes).to have_key(:age)
|
52
52
|
expect(mytype.attributes[:age].type).to eq(PersonBlueprint.attributes[:age].type)
|
@@ -57,42 +57,42 @@ describe Praxis::Blueprint do
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
context 'with a reference, but that does not have a matching attribute name' do
|
60
|
-
let(:myblock)
|
61
|
-
|
60
|
+
let(:myblock) do
|
61
|
+
proc do
|
62
62
|
attributes reference: AddressBlueprint do
|
63
63
|
attribute :age
|
64
64
|
end
|
65
65
|
end
|
66
|
-
|
66
|
+
end
|
67
67
|
it 'fails resolving' do
|
68
|
-
expect{mytype.attributes}.to raise_error(/Type for attribute with name: age could not be determined./)
|
68
|
+
expect { mytype.attributes }.to raise_error(/Type for attribute with name: age could not be determined./)
|
69
69
|
end
|
70
70
|
end
|
71
71
|
context 'without a reference' do
|
72
|
-
let(:myblock)
|
73
|
-
|
72
|
+
let(:myblock) do
|
73
|
+
proc do
|
74
74
|
attributes do
|
75
75
|
attribute :age
|
76
76
|
end
|
77
77
|
end
|
78
|
-
|
78
|
+
end
|
79
79
|
it 'fails resolving' do
|
80
|
-
expect{mytype.attributes}.to raise_error(/Type for attribute with name: age could not be determined./)
|
80
|
+
expect { mytype.attributes }.to raise_error(/Type for attribute with name: age could not be determined./)
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
84
84
|
context 'with block (if it is NOT a leaf)' do
|
85
85
|
context 'that has a reference with an attribute with the same name' do
|
86
86
|
context 'which is not a collection type' do
|
87
|
-
let(:myblock)
|
88
|
-
|
87
|
+
let(:myblock) do
|
88
|
+
proc do
|
89
89
|
attributes reference: PersonBlueprint do
|
90
|
-
attribute :age
|
90
|
+
attribute :age, description: 'I am fully redefining' do
|
91
91
|
attribute :foobar, Integer, min: 42
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
95
|
-
|
95
|
+
end
|
96
96
|
it 'picks Struct, and makes sure to pass the reference of the attribute along' do
|
97
97
|
expect(mytype.attributes).to have_key(:age)
|
98
98
|
age_attribute = mytype.attributes[:age]
|
@@ -101,23 +101,23 @@ describe Praxis::Blueprint do
|
|
101
101
|
# does NOT brings any ref options (except the right reference)
|
102
102
|
expect(age_attribute.options).to include(description: 'I am fully redefining')
|
103
103
|
# Yes, there is no way we can ever use an Integer when we're defining a Struct...but if the parent was a Struct, we would
|
104
|
-
expect(age_attribute.options).to include(reference: Attributor::Integer)
|
104
|
+
expect(age_attribute.options).to include(reference: Attributor::Integer)
|
105
105
|
# And the nested attribute is correctly resolved as well, and ensures options are there
|
106
106
|
expect(age_attribute.type.attributes[:foobar].type).to eq(Attributor::Integer)
|
107
107
|
expect(age_attribute.type.attributes[:foobar].options).to eq(min: 42)
|
108
108
|
end
|
109
109
|
end
|
110
110
|
context 'which is a collection type' do
|
111
|
-
let(:myblock)
|
112
|
-
|
111
|
+
let(:myblock) do
|
112
|
+
proc do
|
113
113
|
attributes reference: PersonBlueprint do
|
114
|
-
attribute :prior_addresses
|
114
|
+
attribute :prior_addresses, description: 'I am fully redefining' do
|
115
115
|
attribute :street, required: true
|
116
116
|
attribute :new_attribute, String, default: 'foo'
|
117
117
|
end
|
118
118
|
end
|
119
119
|
end
|
120
|
-
|
120
|
+
end
|
121
121
|
it 'picks Struct, and makes sure to pass the reference of the attribute along' do
|
122
122
|
expect(mytype.attributes).to have_key(:prior_addresses)
|
123
123
|
prior_addresses_attribute = mytype.attributes[:prior_addresses]
|
@@ -127,7 +127,7 @@ describe Praxis::Blueprint do
|
|
127
127
|
# does NOT brings any ref options (except the right reference)
|
128
128
|
expect(prior_addresses_attribute.options).to include(description: 'I am fully redefining')
|
129
129
|
# Yes, there is no way we can ever use an Integer when we're defining a Struct...but if the parent was a Struct, we would
|
130
|
-
expect(prior_addresses_attribute.options).to include(reference: PersonBlueprint.attributes[:prior_addresses].type.member_type)
|
130
|
+
expect(prior_addresses_attribute.options).to include(reference: PersonBlueprint.attributes[:prior_addresses].type.member_type)
|
131
131
|
# And the nested attributes are correctly resolved as well, and ensures options are there
|
132
132
|
street_options_from_ref = PersonBlueprint.attributes[:prior_addresses].type.member_type.attributes[:street].options
|
133
133
|
expect(prior_addresses_attribute.type.member_type.attributes[:street].type).to eq(Attributor::String)
|
@@ -138,20 +138,20 @@ describe Praxis::Blueprint do
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
context 'in the unlikely case that the reference type has an anonymous Struct (or collection of)' do
|
141
|
-
let(:myblock)
|
142
|
-
|
141
|
+
let(:myblock) do
|
142
|
+
proc do
|
143
143
|
attributes reference: PersonBlueprint do
|
144
144
|
attribute :funny_attribute, description: 'Funny business' do
|
145
145
|
attribute :foobar, Integer, min: 42
|
146
146
|
end
|
147
147
|
end
|
148
148
|
end
|
149
|
-
|
149
|
+
end
|
150
150
|
it 'correctly inherits it (same result as defaulting to Struct) and brings in the reference' do
|
151
151
|
expect(mytype.attributes).to have_key(:funny_attribute)
|
152
152
|
# Resolves to Struct, and brings (and merges) the ref options with the attribute's
|
153
153
|
expect(mytype.attributes[:funny_attribute].type).to be < Attributor::Struct
|
154
|
-
merged_options = {reference: PersonBlueprint.attributes[:funny_attribute].type}.merge(description: 'Funny business')
|
154
|
+
merged_options = { reference: PersonBlueprint.attributes[:funny_attribute].type }.merge(description: 'Funny business')
|
155
155
|
expect(mytype.attributes[:funny_attribute].options).to include(merged_options)
|
156
156
|
# And the nested attribute is correctly resolved as well, and ensures options are there
|
157
157
|
expect(mytype.attributes[:funny_attribute].type.attributes[:foobar].type).to eq(Attributor::Integer)
|
@@ -160,21 +160,21 @@ describe Praxis::Blueprint do
|
|
160
160
|
end
|
161
161
|
end
|
162
162
|
context 'with a reference, but that does not have a matching attribute name' do
|
163
|
-
let(:myblock)
|
164
|
-
|
163
|
+
let(:myblock) do
|
164
|
+
proc do
|
165
165
|
attributes reference: AddressBlueprint do
|
166
166
|
attribute :age, description: 'I am redefining' do
|
167
167
|
attribute :foobar, Integer, min: 42
|
168
168
|
end
|
169
169
|
end
|
170
170
|
end
|
171
|
-
|
171
|
+
end
|
172
172
|
it 'correctly defaults to Struct uses only the local options (same exact as if it had no reference)' do
|
173
173
|
expect(mytype.attributes).to have_key(:age)
|
174
174
|
age_attribute = mytype.attributes[:age]
|
175
175
|
# Resolves to Struct
|
176
176
|
expect(age_attribute.type).to be < Attributor::Struct
|
177
|
-
# does NOT brings any ref options
|
177
|
+
# does NOT brings any ref options
|
178
178
|
expect(age_attribute.options).to eq(description: 'I am redefining')
|
179
179
|
# And the nested attribute is correctly resolved as well, and ensures options are there
|
180
180
|
expect(age_attribute.type.attributes[:foobar].type).to eq(Attributor::Integer)
|
@@ -182,21 +182,21 @@ describe Praxis::Blueprint do
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
context 'without a reference' do
|
185
|
-
let(:myblock)
|
186
|
-
|
185
|
+
let(:myblock) do
|
186
|
+
proc do
|
187
187
|
attributes do
|
188
188
|
attribute :age, description: 'I am redefining' do
|
189
189
|
attribute :foobar, Integer, min: 42
|
190
190
|
end
|
191
191
|
end
|
192
192
|
end
|
193
|
-
|
193
|
+
end
|
194
194
|
it 'correctly defaults to Struct uses only the local options' do
|
195
195
|
expect(mytype.attributes).to have_key(:age)
|
196
196
|
age_attribute = mytype.attributes[:age]
|
197
197
|
# Resolves to Struct
|
198
198
|
expect(age_attribute.type).to be < Attributor::Struct
|
199
|
-
# does NOT brings any ref options
|
199
|
+
# does NOT brings any ref options
|
200
200
|
expect(age_attribute.options).to eq(description: 'I am redefining')
|
201
201
|
# And the nested attribute is correctly resolved as well, and ensures options are there
|
202
202
|
expect(age_attribute.type.attributes[:foobar].type).to eq(Attributor::Integer)
|
@@ -207,43 +207,43 @@ describe Praxis::Blueprint do
|
|
207
207
|
end
|
208
208
|
context 'with an explicit type specified' do
|
209
209
|
context 'without a reference' do
|
210
|
-
let(:myblock)
|
211
|
-
|
210
|
+
let(:myblock) do
|
211
|
+
proc do
|
212
212
|
attributes do
|
213
213
|
attribute :age, String, description: 'I am a String now'
|
214
214
|
end
|
215
215
|
end
|
216
|
-
|
216
|
+
end
|
217
217
|
it 'always uses the provided type and local options specified' do
|
218
218
|
expect(mytype.attributes).to have_key(:age)
|
219
219
|
age_attribute = mytype.attributes[:age]
|
220
220
|
# Resolves to String
|
221
221
|
expect(age_attribute.type).to eq(Attributor::String)
|
222
222
|
# copies local options
|
223
|
-
expect(age_attribute.options).to
|
223
|
+
expect(age_attribute.options).to eq(description: 'I am a String now')
|
224
224
|
end
|
225
225
|
end
|
226
226
|
context 'with a reference' do
|
227
|
-
let(:myblock)
|
228
|
-
|
227
|
+
let(:myblock) do
|
228
|
+
proc do
|
229
229
|
attributes reference: PersonBlueprint do
|
230
230
|
attribute :age, String, description: 'I am a String now'
|
231
231
|
end
|
232
232
|
end
|
233
|
-
|
233
|
+
end
|
234
234
|
it 'always uses the provided type and local options specified (same as if it had no reference)' do
|
235
235
|
expect(mytype.attributes).to have_key(:age)
|
236
236
|
age_attribute = mytype.attributes[:age]
|
237
237
|
# Resolves to String
|
238
238
|
expect(age_attribute.type).to eq(Attributor::String)
|
239
239
|
# copies local options
|
240
|
-
expect(age_attribute.options).to
|
240
|
+
expect(age_attribute.options).to eq(description: 'I am a String now')
|
241
241
|
end
|
242
242
|
end
|
243
243
|
|
244
244
|
context 'with a reference, which can further percolate down' do
|
245
|
-
let(:myblock)
|
246
|
-
|
245
|
+
let(:myblock) do
|
246
|
+
proc do
|
247
247
|
attributes reference: PersonBlueprint do
|
248
248
|
attribute :age, String, description: 'I am a String now'
|
249
249
|
attribute :address, Struct, description: 'Address subset' do
|
@@ -252,13 +252,13 @@ describe Praxis::Blueprint do
|
|
252
252
|
attribute :tags
|
253
253
|
end
|
254
254
|
end
|
255
|
-
|
256
|
-
|
255
|
+
end
|
256
|
+
|
257
257
|
it 'brings the child reference for address so we can redefine it' do
|
258
|
-
expect(mytype.attributes.keys).to eq([
|
258
|
+
expect(mytype.attributes.keys).to eq(%i[age address tags])
|
259
259
|
age_attribute = mytype.attributes[:age]
|
260
260
|
expect(age_attribute.type).to eq(Attributor::String)
|
261
|
-
expect(age_attribute.options).to
|
261
|
+
expect(age_attribute.options).to eq(description: 'I am a String now')
|
262
262
|
|
263
263
|
address_attribute = mytype.attributes[:address]
|
264
264
|
expect(address_attribute.type).to be < Attributor::Struct
|
@@ -273,7 +273,7 @@ describe Praxis::Blueprint do
|
|
273
273
|
expect(street_attribute.options).to include(required: true)
|
274
274
|
# And brings in other options from the inherited street attribute
|
275
275
|
expect(street_attribute.options).to include(description: 'The street')
|
276
|
-
|
276
|
+
|
277
277
|
# It also properly resolves the direct tags attribute from the reference, pointing to the same type
|
278
278
|
tags_attribute = mytype.attributes[:tags]
|
279
279
|
expect(tags_attribute.type).to eq PersonBlueprint.attributes[:tags].type
|
@@ -441,7 +441,7 @@ describe Praxis::Blueprint do
|
|
441
441
|
end
|
442
442
|
|
443
443
|
context 'with a valid nested blueprint' do
|
444
|
-
let(:hash) { { name: 'bob', myself: { name: 'PseudoBob'}} }
|
444
|
+
let(:hash) { { name: 'bob', myself: { name: 'PseudoBob' } } }
|
445
445
|
|
446
446
|
it { should be_empty }
|
447
447
|
end
|
@@ -454,14 +454,12 @@ describe Praxis::Blueprint do
|
|
454
454
|
end
|
455
455
|
|
456
456
|
context 'with an invalid nested blueprint' do
|
457
|
-
let(:hash) { { name: 'bob', myself: { name: 'PseudoBob', address: { state: 'ME' }}} }
|
457
|
+
let(:hash) { { name: 'bob', myself: { name: 'PseudoBob', address: { state: 'ME' } } } }
|
458
458
|
|
459
459
|
it { should have(1).item }
|
460
460
|
its(:first) { should =~ /Attribute \$.myself.address.state/ }
|
461
|
-
|
462
461
|
end
|
463
462
|
|
464
|
-
|
465
463
|
context 'for objects of the wrong type' do
|
466
464
|
it 'raises an error' do
|
467
465
|
expect do
|
@@ -23,6 +23,10 @@ describe Praxis::Controller do
|
|
23
23
|
def index; end
|
24
24
|
|
25
25
|
def show; end
|
26
|
+
|
27
|
+
def self.to_s
|
28
|
+
'SomeController'
|
29
|
+
end
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
@@ -34,7 +38,6 @@ describe Praxis::Controller do
|
|
34
38
|
|
35
39
|
describe '#inspect' do
|
36
40
|
it 'includes name, object ID and request' do
|
37
|
-
SomeController = subject # de-anonymize class name
|
38
41
|
expect(subject.new('eioio').inspect).to match(
|
39
42
|
/#<SomeController#[0-9]+ @request="eioio">/
|
40
43
|
)
|
@@ -35,8 +35,8 @@ describe Praxis::Extensions::FieldExpansion do
|
|
35
35
|
|
36
36
|
let(:test_attributes) {}
|
37
37
|
let(:test_params) { double('test_params', attributes: test_attributes) }
|
38
|
-
|
39
|
-
subject(:expansion) { test_instance.expanded_fields(request, media_type) }
|
38
|
+
let(:expansion_filter) { nil }
|
39
|
+
subject(:expansion) { test_instance.expanded_fields(request, media_type, expansion_filter) }
|
40
40
|
|
41
41
|
context '#expanded_fields' do
|
42
42
|
context 'with fields and view params defined' do
|
@@ -62,8 +62,6 @@ describe Praxis::Extensions::Pagination::OrderingParams do
|
|
62
62
|
it 'enforces only first components' do
|
63
63
|
# It allows non-defined field in second position
|
64
64
|
expect(order_attr.load('name,recent_posts.title').validate).to be_empty
|
65
|
-
# It does not allow non-defined field in first position
|
66
|
-
expect(order_attr.load('recent_posts.title,name').validate).to_not be_empty
|
67
65
|
end
|
68
66
|
end
|
69
67
|
end
|