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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +0 -5
  4. data/.travis.yml +2 -3
  5. data/Appraisals +6 -4
  6. data/CHANGELOG.md +10 -0
  7. data/Gemfile +6 -6
  8. data/gemfiles/active_6.gemfile +11 -9
  9. data/gemfiles/active_6.gemfile.lock +21 -15
  10. data/gemfiles/active_7.gemfile +11 -9
  11. data/gemfiles/active_7.gemfile.lock +21 -15
  12. data/lib/praxis/application.rb +3 -0
  13. data/lib/praxis/blueprint.rb +2 -1
  14. data/lib/praxis/docs/open_api/paths_object.rb +1 -1
  15. data/lib/praxis/docs/open_api/schema_object.rb +48 -27
  16. data/lib/praxis/docs/open_api_generator.rb +6 -6
  17. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +0 -1
  18. data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +1 -1
  19. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +5 -0
  20. data/lib/praxis/extensions/field_expansion.rb +7 -4
  21. data/lib/praxis/extensions/pagination/ordering_params.rb +0 -4
  22. data/lib/praxis/field_expander.rb +18 -3
  23. data/lib/praxis/handlers/xml_sample.rb +1 -1
  24. data/lib/praxis/mapper/resource.rb +1 -1
  25. data/lib/praxis/mapper/selector_generator.rb +1 -1
  26. data/lib/praxis/multipart/parser.rb +1 -1
  27. data/lib/praxis/types/splattable_string_array.rb +13 -0
  28. data/lib/praxis/version.rb +1 -1
  29. data/lib/praxis.rb +1 -1
  30. data/praxis.gemspec +3 -3
  31. data/spec/functional_library_spec.rb +91 -28
  32. data/spec/praxis/application_spec.rb +7 -2
  33. data/spec/praxis/blueprint_spec.rb +53 -55
  34. data/spec/praxis/controller_spec.rb +4 -1
  35. data/spec/praxis/extensions/field_expansion_spec.rb +2 -2
  36. data/spec/praxis/extensions/pagination/ordering_params_spec.rb +0 -2
  37. data/spec/praxis/field_expander_spec.rb +46 -0
  38. data/spec/spec_app/app/controllers/base_class.rb +12 -0
  39. data/spec/spec_app/app/resources/book.rb +9 -0
  40. data/spec/spec_app/config.ru +0 -1
  41. data/spec/spec_app/design/media_types/book.rb +2 -0
  42. data/spec/spec_app/design/resources/authors.rb +3 -7
  43. data/spec/support/spec_blueprints.rb +15 -0
  44. data/tasks/thor/templates/generator/example_app/Gemfile +1 -1
  45. data/tasks/thor/templates/generator/example_app/app/models/user.rb +0 -1
  46. data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +0 -1
  47. data/tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb +10 -4
  48. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +24 -0
  49. data/tasks/thor/templates/generator/example_app/design/api.rb +12 -1
  50. data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +1 -1
  51. data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +1 -1
  52. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -1
  53. metadata +18 -17
  54. data/.simplecov +0 -9
@@ -48,7 +48,7 @@ module Praxis
48
48
 
49
49
  case type
50
50
  when nil
51
- if (node.children.size == 1 && node.child.text?) || node.children.size.zero?
51
+ if (node.children.size == 1 && node.child.text?) || node.children.empty?
52
52
  # leaf text node
53
53
  node.content
54
54
  else
@@ -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) # rubocop:disable Naming/MethodParameterName
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 }
@@ -47,7 +47,7 @@ module Praxis
47
47
  end
48
48
 
49
49
  def dig(...)
50
- @fields.dig(...) # rubocop:disable Style/SingleArgumentDig
50
+ @fields.dig(...)
51
51
  end
52
52
 
53
53
  def [](*path)
@@ -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 && BUFSIZE >= @content_length ? @content_length : BUFSIZE)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Praxis
4
- VERSION = '2.0.pre.33'
4
+ VERSION = '2.0.pre.34'
5
5
  end
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.0'
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 'coveralls'
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
- it 'is successful' do
152
- expect(subject).to be_successful
153
- expect(subject.headers['Content-Type']).to eq('application/vnd.acme.author; type=collection')
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
- expect(parsed_response.size).to eq base_query.count
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 'using nested attributes' do
163
- let(:order_q) { '-name,books.name' }
164
- it 'is successful' do
165
- expect(subject).to be_successful
166
- ids = base_query.order('active_authors.name DESC', 'active_books.simple_name DESC').pluck(:id)
167
-
168
- expect(parsed_response.map { |book| book[:id] }).to eq ids
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
- end
172
- context 'filtering and sorting' do
173
- context 'using the same tables, including the base query one' do
174
- let(:order_q) { '-name,books.name' }
175
- let(:filters_q) { 'books.name!' }
176
- let(:fields_q) { 'id,books{name}' }
177
- it 'is successful' do
178
- expect(subject).to be_successful
179
- ids = base_query.order('active_authors.name DESC', 'active_books.simple_name DESC').pluck(:id)
180
-
181
- expect(parsed_response.map { |book| book[:id] }).to eq ids
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) { Class.new(Praxis::Application) }
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{|c| c._finalize!}
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
- Proc.new do
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
- Proc.new do
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
- Proc.new do
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
- Proc.new do
87
+ let(:myblock) do
88
+ proc do
89
89
  attributes reference: PersonBlueprint do
90
- attribute :age , description: 'I am fully redefining' do
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
- Proc.new do
111
+ let(:myblock) do
112
+ proc do
113
113
  attributes reference: PersonBlueprint do
114
- attribute :prior_addresses , description: 'I am fully redefining' do
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
- Proc.new do
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
- Proc.new do
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
- Proc.new do
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
- Proc.new do
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 eq(description: 'I am a String now')
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
- Proc.new do
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 eq(description: 'I am a String now')
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
- Proc.new do
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([:age, :address, :tags])
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 eq(description: 'I am a String now')
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