praxis 0.22.pre.2 → 2.0.pre.1
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 +4 -4
- data/CHANGELOG.md +323 -324
- data/lib/praxis/action_definition.rb +7 -9
- data/lib/praxis/api_definition.rb +27 -44
- data/lib/praxis/api_general_info.rb +2 -3
- data/lib/praxis/application.rb +14 -141
- data/lib/praxis/bootloader.rb +1 -2
- data/lib/praxis/bootloader_stages/environment.rb +13 -0
- data/lib/praxis/controller.rb +0 -2
- data/lib/praxis/dispatcher.rb +4 -6
- data/lib/praxis/docs/generator.rb +8 -18
- data/lib/praxis/docs/link_builder.rb +1 -1
- data/lib/praxis/error_handler.rb +5 -5
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +1 -1
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +16 -18
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +5 -5
- data/lib/praxis/extensions/field_selection.rb +1 -12
- data/lib/praxis/extensions/rendering.rb +1 -1
- data/lib/praxis/file_group.rb +1 -1
- data/lib/praxis/handlers/xml.rb +1 -1
- data/lib/praxis/mapper/active_model_compat.rb +63 -0
- data/lib/praxis/mapper/resource.rb +242 -0
- data/lib/praxis/mapper/selector_generator.rb +126 -0
- data/lib/praxis/mapper/sequel_compat.rb +37 -0
- data/lib/praxis/middleware_app.rb +13 -15
- data/lib/praxis/multipart/part.rb +3 -5
- data/lib/praxis/plugins/mapper_plugin.rb +50 -0
- data/lib/praxis/request.rb +14 -7
- data/lib/praxis/request_stages/response.rb +2 -3
- data/lib/praxis/resource_definition.rb +10 -14
- data/lib/praxis/response.rb +6 -5
- data/lib/praxis/response_definition.rb +5 -7
- data/lib/praxis/response_template.rb +3 -4
- data/lib/praxis/responses/http.rb +36 -0
- data/lib/praxis/responses/internal_server_error.rb +12 -3
- data/lib/praxis/responses/multipart_ok.rb +11 -4
- data/lib/praxis/responses/validation_error.rb +10 -1
- data/lib/praxis/router.rb +3 -3
- data/lib/praxis/tasks/api_docs.rb +2 -10
- data/lib/praxis/tasks/routes.rb +0 -1
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +13 -9
- data/praxis.gemspec +2 -3
- data/spec/functional_spec.rb +0 -1
- data/spec/praxis/action_definition_spec.rb +15 -26
- data/spec/praxis/api_definition_spec.rb +8 -13
- data/spec/praxis/api_general_info_spec.rb +8 -3
- data/spec/praxis/application_spec.rb +7 -13
- data/spec/praxis/handlers/xml_spec.rb +2 -2
- data/spec/praxis/mapper/resource_spec.rb +169 -0
- data/spec/praxis/mapper/selector_generator_spec.rb +301 -0
- data/spec/praxis/middleware_app_spec.rb +15 -9
- data/spec/praxis/request_spec.rb +7 -17
- data/spec/praxis/request_stages/validate_spec.rb +1 -1
- data/spec/praxis/resource_definition_spec.rb +10 -12
- data/spec/praxis/response_definition_spec.rb +5 -22
- data/spec/praxis/response_spec.rb +5 -12
- data/spec/praxis/responses/internal_server_error_spec.rb +5 -2
- data/spec/praxis/router_spec.rb +4 -8
- data/spec/spec_app/app/models/person.rb +3 -3
- data/spec/spec_app/config/environment.rb +3 -21
- data/spec/spec_app/config.ru +6 -1
- data/spec/spec_helper.rb +2 -17
- data/spec/support/spec_resources.rb +131 -0
- metadata +19 -31
- data/lib/praxis/extensions/attribute_filtering/query_builder.rb +0 -39
- data/lib/praxis/extensions/attribute_filtering.rb +0 -28
- data/lib/praxis/extensions/mapper_selectors.rb +0 -16
- data/lib/praxis/media_type_collection.rb +0 -127
- data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
- data/spec/praxis/media_type_collection_spec.rb +0 -157
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +0 -142
data/lib/praxis/router.rb
CHANGED
@@ -50,7 +50,7 @@ module Praxis
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def add_route(target, route)
|
53
|
-
path_versioning = (
|
53
|
+
path_versioning = (Application.instance.versioning_scheme == :path)
|
54
54
|
|
55
55
|
# DEPRECATED: remove with ResourceDefinition.version using: :path
|
56
56
|
path_versioning ||= (target.action.resource_definition.version_options[:using] == :path)
|
@@ -65,7 +65,7 @@ module Praxis
|
|
65
65
|
def call(env_or_request)
|
66
66
|
request = case env_or_request
|
67
67
|
when Hash
|
68
|
-
request_class.new(env_or_request
|
68
|
+
request_class.new(env_or_request)
|
69
69
|
when request_class
|
70
70
|
env_or_request
|
71
71
|
else
|
@@ -101,7 +101,7 @@ module Praxis
|
|
101
101
|
body += " Available versions = #{pretty_versions}."
|
102
102
|
end
|
103
103
|
headers = {"Content-Type" => "text/plain"}
|
104
|
-
if
|
104
|
+
if Praxis::Application.instance.config.praxis.x_cascade
|
105
105
|
headers['X-Cascade'] = 'pass'
|
106
106
|
end
|
107
107
|
result = [404, headers, [body]]
|
@@ -58,16 +58,8 @@ namespace :praxis do
|
|
58
58
|
require 'fileutils'
|
59
59
|
|
60
60
|
Praxis::Blueprint.caching_enabled = false
|
61
|
-
|
62
|
-
|
63
|
-
if apps.size == 1
|
64
|
-
# Backwards compatible directory generation when there's only 1 app
|
65
|
-
Praxis::Docs::Generator.generate(Dir.pwd, name: apps.first[0], skip_sub_directory: true)
|
66
|
-
else
|
67
|
-
apps.each do|name,instance|
|
68
|
-
Praxis::Docs::Generator.generate(Dir.pwd, name: name)
|
69
|
-
end
|
70
|
-
end
|
61
|
+
generator = Praxis::Docs::Generator.new(Dir.pwd)
|
62
|
+
generator.save!
|
71
63
|
end
|
72
64
|
|
73
65
|
end
|
data/lib/praxis/tasks/routes.rb
CHANGED
data/lib/praxis/version.rb
CHANGED
data/lib/praxis.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require 'attributor'
|
3
|
-
require 'praxis-mapper'
|
4
3
|
require 'praxis-blueprints'
|
5
4
|
|
6
5
|
require 'active_support/concern'
|
7
6
|
require 'praxis/request_superclassing'
|
7
|
+
require 'active_support/inflector'
|
8
8
|
|
9
9
|
$:.unshift File.dirname(__FILE__)
|
10
10
|
|
@@ -62,7 +62,6 @@ module Praxis
|
|
62
62
|
|
63
63
|
autoload :Links, 'praxis/links'
|
64
64
|
autoload :MediaType, 'praxis/media_type'
|
65
|
-
autoload :MediaTypeCollection, 'praxis/media_type_collection'
|
66
65
|
autoload :MediaTypeIdentifier, 'praxis/media_type_identifier'
|
67
66
|
autoload :Multipart, 'praxis/types/multipart'
|
68
67
|
autoload :Collection, 'praxis/collection'
|
@@ -89,7 +88,8 @@ module Praxis
|
|
89
88
|
autoload :MapperSelectors, 'praxis/extensions/mapper_selectors'
|
90
89
|
autoload :Rendering, 'praxis/extensions/rendering'
|
91
90
|
autoload :FieldExpansion, 'praxis/extensions/field_expansion'
|
92
|
-
autoload :
|
91
|
+
autoload :ActiveRecordFilterQueryBuilder, 'praxis/extensions/attribute_filtering/active_record_filter_query_builder'
|
92
|
+
autoload :SequelFilterQueryBuilder, 'praxis/extensions/attribute_filtering/sequel_filter_query_builder'
|
93
93
|
end
|
94
94
|
|
95
95
|
module Handlers
|
@@ -123,11 +123,15 @@ module Praxis
|
|
123
123
|
autoload :Response, 'praxis/request_stages/response'
|
124
124
|
end
|
125
125
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
#
|
126
|
+
module Mapper
|
127
|
+
autoload :Resource, 'praxis/mapper/resource'
|
128
|
+
autoload :SelectorGenerator, 'praxis/mapper/selector_generator'
|
129
|
+
end
|
130
|
+
|
131
|
+
# Avoid loading responses (and templates) lazily as they need to be registered in time
|
132
|
+
require 'praxis/responses/http'
|
133
|
+
require 'praxis/responses/internal_server_error'
|
134
|
+
require 'praxis/responses/validation_error'
|
135
|
+
require 'praxis/responses/multipart_ok'
|
132
136
|
|
133
137
|
end
|
data/praxis.gemspec
CHANGED
@@ -24,10 +24,9 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency 'mustermann', '>=0', '<=1'
|
25
25
|
spec.add_dependency 'activesupport', '>= 3'
|
26
26
|
spec.add_dependency 'mime', '~> 0'
|
27
|
-
spec.add_dependency 'praxis-mapper', '~> 4.3'
|
28
27
|
spec.add_dependency 'praxis-blueprints', '~> 3.3'
|
29
28
|
spec.add_dependency 'attributor', '~> 5.1'
|
30
|
-
spec.add_dependency 'thor'
|
29
|
+
spec.add_dependency 'thor'
|
31
30
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
32
31
|
|
33
32
|
spec.add_development_dependency 'bundler'
|
@@ -50,6 +49,6 @@ Gem::Specification.new do |spec|
|
|
50
49
|
spec.add_development_dependency 'rack-test', '~> 0'
|
51
50
|
spec.add_development_dependency 'simplecov', '~> 0'
|
52
51
|
spec.add_development_dependency 'fuubar', '~> 2'
|
53
|
-
spec.add_development_dependency 'yard',
|
52
|
+
spec.add_development_dependency 'yard', ">= 0.9.20"
|
54
53
|
spec.add_development_dependency 'coveralls'
|
55
54
|
end
|
data/spec/functional_spec.rb
CHANGED
@@ -136,7 +136,6 @@ describe 'Functional specs' do
|
|
136
136
|
headers = last_response.headers
|
137
137
|
expect(headers['Content-Type']).to eq('application/json')
|
138
138
|
expect(headers['Spec-Middleware']).to eq('used')
|
139
|
-
expect(headers['Content-Length']).to eq(last_response.body.size.to_s)
|
140
139
|
end
|
141
140
|
|
142
141
|
it 'returns early when making the before filter break' do
|
@@ -1,15 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Praxis::ActionDefinition do
|
4
|
-
|
5
|
-
before(:context) do
|
6
|
-
Thread.current[:praxis_instance] = Praxis::Application.new(name: 'action_definition_spec', skip_registration: true)
|
7
|
-
end
|
8
|
-
|
9
|
-
let(:praxis_instance) do
|
10
|
-
Thread.current[:praxis_instance]
|
11
|
-
end
|
12
|
-
|
13
4
|
class SpecMediaType < Praxis::MediaType
|
14
5
|
identifier 'application/json'
|
15
6
|
|
@@ -40,8 +31,10 @@ describe Praxis::ActionDefinition do
|
|
40
31
|
end
|
41
32
|
end
|
42
33
|
end
|
34
|
+
|
43
35
|
subject(:action) do
|
44
|
-
|
36
|
+
|
37
|
+
Praxis::ApiDefinition.define do |api|
|
45
38
|
api.response_template :ok do |media_type: , location: nil, headers: nil, description: nil |
|
46
39
|
status 200
|
47
40
|
|
@@ -49,10 +42,6 @@ describe Praxis::ActionDefinition do
|
|
49
42
|
location location
|
50
43
|
headers headers if headers
|
51
44
|
end
|
52
|
-
|
53
|
-
api.info do # applies to all API infos
|
54
|
-
base_path "/pref"
|
55
|
-
end
|
56
45
|
end
|
57
46
|
Praxis::ActionDefinition.new(:foo, resource_definition) do
|
58
47
|
routing { get '/:one' }
|
@@ -122,11 +111,11 @@ describe Praxis::ActionDefinition do
|
|
122
111
|
let(:traits) { {test: trait} }
|
123
112
|
|
124
113
|
before do
|
125
|
-
allow(
|
114
|
+
allow(Praxis::ApiDefinition.instance).to receive(:traits).and_return(traits)
|
126
115
|
end
|
127
116
|
|
128
117
|
its('params.attributes.keys') { should eq [:inherited, :app_name, :name, :one]}
|
129
|
-
its('routes.first.path.to_s') { should eq '/
|
118
|
+
its('routes.first.path.to_s') { should eq '/api/foobars/hello_world/test_trait/:app_name/:one' }
|
130
119
|
its(:traits) { should eq [:test] }
|
131
120
|
|
132
121
|
it 'is reflected in the describe output' do
|
@@ -326,9 +315,11 @@ describe Praxis::ActionDefinition do
|
|
326
315
|
end
|
327
316
|
|
328
317
|
context 'with a base_path and base_params on ApiDefinition' do
|
329
|
-
|
330
|
-
|
331
|
-
|
318
|
+
# Without getting a fresh new ApiDefinition it is very difficult to test stuff using the Singleton
|
319
|
+
# So for some tests we're gonna create a new instance and work with it to avoid the singleton issues
|
320
|
+
let(:non_singleton_api) do
|
321
|
+
api_def=Praxis::ApiDefinition.__send__(:new)
|
322
|
+
api_def.instance_eval do |api|
|
332
323
|
|
333
324
|
api.info do
|
334
325
|
base_path '/apps/:app_name'
|
@@ -341,13 +332,11 @@ describe Praxis::ActionDefinition do
|
|
341
332
|
end
|
342
333
|
|
343
334
|
end
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
params { attribute :one, String }
|
350
|
-
end
|
335
|
+
api_def
|
336
|
+
end
|
337
|
+
|
338
|
+
before do
|
339
|
+
allow(Praxis::ApiDefinition).to receive(:instance).and_return(non_singleton_api)
|
351
340
|
end
|
352
341
|
|
353
342
|
its('routes.first.path.to_s') { should eq '/apps/:app_name/foobars/hello_world/:one' }
|
@@ -2,18 +2,12 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Praxis::ApiDefinition do
|
4
4
|
|
5
|
-
|
6
|
-
subject(:api){ Praxis::ApiDefinition.instance }
|
5
|
+
subject(:api){ Praxis::ApiDefinition.instance }
|
7
6
|
|
8
7
|
# Without getting a fresh new ApiDefinition it is very difficult to test stuff using the Singleton
|
9
8
|
# So for some tests we're gonna create a new instance and work with it to avoid the singleton issues
|
10
|
-
let(:app_instance){ double("AppInstance") }
|
11
|
-
before do
|
12
|
-
allow(app_instance).to receive(:versioning_scheme=).with([:header, :params])
|
13
|
-
end
|
14
|
-
|
15
9
|
let(:non_singleton_api) do
|
16
|
-
api_def=Praxis::ApiDefinition.new
|
10
|
+
api_def=Praxis::ApiDefinition.__send__(:new)
|
17
11
|
api_def.instance_eval do |api|
|
18
12
|
api.response_template :template1, &Proc.new {}
|
19
13
|
api.trait :trait1, &Proc.new {}
|
@@ -38,7 +32,12 @@ describe Praxis::ApiDefinition do
|
|
38
32
|
end
|
39
33
|
end
|
40
34
|
|
41
|
-
context '
|
35
|
+
context 'singleton' do
|
36
|
+
it 'should be a Singleton' do
|
37
|
+
expect(Praxis::ApiDefinition.ancestors).to include( Singleton )
|
38
|
+
expect(subject).to eq(Praxis::ApiDefinition.instance )
|
39
|
+
end
|
40
|
+
|
42
41
|
it 'has the :ok and :created response templates registered' do
|
43
42
|
expect(api.responses.keys).to include(:ok)
|
44
43
|
expect(api.responses.keys).to include(:created)
|
@@ -59,10 +58,6 @@ describe Praxis::ApiDefinition do
|
|
59
58
|
expect(api.responses.keys).to include(:foobar)
|
60
59
|
expect(api.response(:foobar)).to be_kind_of(Praxis::ResponseTemplate)
|
61
60
|
end
|
62
|
-
it 'stores the app instance' do
|
63
|
-
template = api.response(:template1)
|
64
|
-
expect(template.application).to be(app_instance)
|
65
|
-
end
|
66
61
|
end
|
67
62
|
|
68
63
|
context '.response' do
|
@@ -2,7 +2,12 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Praxis::ApiGeneralInfo do
|
4
4
|
|
5
|
-
subject(:info){ Praxis::ApiGeneralInfo.new
|
5
|
+
subject(:info){ Praxis::ApiGeneralInfo.new }
|
6
|
+
|
7
|
+
before do
|
8
|
+
allow(Praxis::Application.instance).to receive(:versioning_scheme=).with([:header, :params])
|
9
|
+
end
|
10
|
+
|
6
11
|
|
7
12
|
let(:info_block) do
|
8
13
|
Proc.new do
|
@@ -59,8 +64,8 @@ describe Praxis::ApiGeneralInfo do
|
|
59
64
|
end
|
60
65
|
|
61
66
|
context 'base_path with versioning' do
|
62
|
-
let(:global_info){ Praxis::ApiGeneralInfo.new
|
63
|
-
subject(:info){ Praxis::ApiGeneralInfo.new(global_info,
|
67
|
+
let(:global_info){ Praxis::ApiGeneralInfo.new }
|
68
|
+
subject(:info){ Praxis::ApiGeneralInfo.new(global_info, version: '1.0') }
|
64
69
|
|
65
70
|
before do
|
66
71
|
global_info
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Praxis::Application do
|
4
4
|
context 'configuration' do
|
5
5
|
subject(:app) do
|
6
|
-
app = Class.new(Praxis::Application).
|
6
|
+
app = Class.new(Praxis::Application).instance
|
7
7
|
|
8
8
|
config = Object.new
|
9
9
|
def config.define(key=nil, type=Attributor::Struct, **opts, &block)
|
@@ -45,7 +45,7 @@ describe Praxis::Application do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
context 'media type handlers' do
|
48
|
-
subject { Class.new(Praxis::Application).
|
48
|
+
subject { Class.new(Praxis::Application).instance }
|
49
49
|
|
50
50
|
before do
|
51
51
|
# don't actually bootload; we're merely running specs
|
@@ -94,14 +94,12 @@ describe Praxis::Application do
|
|
94
94
|
end
|
95
95
|
|
96
96
|
describe '#setup' do
|
97
|
-
subject { Praxis::Application.
|
98
|
-
|
99
|
-
let(:builder) { double("Builder", to_app: double('Rack app'), run: true) }
|
100
|
-
|
97
|
+
subject { Class.new(Praxis::Application).instance }
|
98
|
+
|
101
99
|
before do
|
102
|
-
# don't actually bootload; we're merely running specs
|
103
|
-
allow(subject).to receive(:
|
104
|
-
allow(subject).to receive(:
|
100
|
+
# don't actually bootload; we're merely running specs
|
101
|
+
allow(subject.bootloader).to receive(:setup!).and_return(true)
|
102
|
+
allow(subject.builder).to receive(:to_app).and_return(double('Rack app'))
|
105
103
|
end
|
106
104
|
|
107
105
|
it 'is idempotent' do
|
@@ -114,9 +112,5 @@ describe Praxis::Application do
|
|
114
112
|
expect(subject.setup).to eq(subject)
|
115
113
|
expect(subject.setup).to eq(subject)
|
116
114
|
end
|
117
|
-
|
118
|
-
it 'creates an ApiDefinition that has a back-reference' do
|
119
|
-
expect(subject.api_definition.application).to eq(subject)
|
120
|
-
end
|
121
115
|
end
|
122
116
|
end
|
@@ -128,7 +128,7 @@ describe Praxis::Handlers::XML do
|
|
128
128
|
it_behaves_like 'xml something'
|
129
129
|
end
|
130
130
|
context 'a array with elements of all types' do
|
131
|
-
let(:parsed){ ["just text",:a,1,BigDecimal
|
131
|
+
let(:parsed){ ["just text",:a,1,BigDecimal(100),0.1,true,Date.new] }
|
132
132
|
it_behaves_like 'xml something'
|
133
133
|
end
|
134
134
|
context 'a hash with a complex substructure' do
|
@@ -137,7 +137,7 @@ describe Praxis::Handlers::XML do
|
|
137
137
|
"text" => "just text",
|
138
138
|
"symbol" => :a,
|
139
139
|
"num" => 1,
|
140
|
-
"bd" => BigDecimal
|
140
|
+
"bd" => BigDecimal(100),
|
141
141
|
"float" => 0.1,
|
142
142
|
"truthyness" => true,
|
143
143
|
"day" => Date.new,
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::Mapper::Resource do
|
4
|
+
let(:parent_record) { ParentModel.new(id: 100, name: 'george sr') }
|
5
|
+
let(:parent_records) { [ParentModel.new(id: 101, name: "georgia"),ParentModel.new(id: 102, name: 'georgina')] }
|
6
|
+
let(:record) { SimpleModel.new(id: 103, name: 'george xvi') }
|
7
|
+
let(:model) { SimpleModel}
|
8
|
+
|
9
|
+
context 'configuration' do
|
10
|
+
subject(:resource) { SimpleResource }
|
11
|
+
its(:model) { should == model }
|
12
|
+
|
13
|
+
context 'properties' do
|
14
|
+
subject(:properties) { resource.properties }
|
15
|
+
|
16
|
+
it 'includes directly-set properties' do
|
17
|
+
expect(properties[:other_resource]).to eq(dependencies: [:other_model])
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'inherits from a superclass' do
|
21
|
+
expect(properties[:href]).to eq(dependencies: [:id])
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'properly overrides a property from the parent' do
|
25
|
+
expect(properties[:name]).to eq(dependencies: [:simple_name])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'retrieving resources' do
|
31
|
+
context 'getting a single resource' do
|
32
|
+
before do
|
33
|
+
expect(SimpleModel).to receive(:get).with(name: 'george xvi').and_return(record)
|
34
|
+
end
|
35
|
+
|
36
|
+
subject(:resource) { SimpleResource.get(:name => 'george xvi') }
|
37
|
+
|
38
|
+
it { is_expected.to be_kind_of(SimpleResource) }
|
39
|
+
|
40
|
+
its(:record) { should be record }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'getting multiple resources' do
|
44
|
+
before do
|
45
|
+
expect(SimpleModel).to receive(:all).with(name: ['george xvi']).and_return([record])
|
46
|
+
end
|
47
|
+
|
48
|
+
subject(:resource_collection) { SimpleResource.all(:name => ["george xvi"]) }
|
49
|
+
|
50
|
+
it { is_expected.to be_kind_of(Array) }
|
51
|
+
|
52
|
+
it 'fetches the models and wraps them' do
|
53
|
+
resource = resource_collection.first
|
54
|
+
expect(resource).to be_kind_of(SimpleResource)
|
55
|
+
expect(resource.record).to eq record
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'delegating to the underlying model' do
|
61
|
+
|
62
|
+
subject { SimpleResource.new(record) }
|
63
|
+
|
64
|
+
it 'does respond_to attributes in the model' do
|
65
|
+
expect(subject).to respond_to(:name)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'does not respond_to :id if the model does not have it' do
|
69
|
+
resource = OtherResource.new(OtherModel.new(:name => "foo"))
|
70
|
+
expect(resource).not_to respond_to(:id)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns raw results for simple attributes' do
|
74
|
+
expect(record).to receive(:name).and_call_original
|
75
|
+
expect(subject.name).to eq("george xvi")
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'wraps model objects in Resource instances' do
|
79
|
+
expect(record).to receive(:parent).and_return(parent_record)
|
80
|
+
|
81
|
+
parent = subject.parent
|
82
|
+
|
83
|
+
expect(parent).to be_kind_of(ParentResource)
|
84
|
+
expect(parent.name).to eq("george sr")
|
85
|
+
expect(parent.record).to eq(parent_record)
|
86
|
+
end
|
87
|
+
|
88
|
+
context "for serialized array associations" do
|
89
|
+
let(:record) { YamlArrayModel.new(:id => 1)}
|
90
|
+
|
91
|
+
subject { YamlArrayResource.new(record)}
|
92
|
+
|
93
|
+
it 'wraps arrays of model objects in an array of resource instances' do
|
94
|
+
expect(record).to receive(:parents).and_return(parent_records)
|
95
|
+
|
96
|
+
parents = subject.parents
|
97
|
+
expect(parents).to have(parent_records.size).items
|
98
|
+
expect(parents).to be_kind_of(Array)
|
99
|
+
|
100
|
+
parents.each { |parent| expect(parent).to be_kind_of(ParentResource) }
|
101
|
+
expect(parents.collect { |parent| parent.record }).to match_array(parent_records)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'resource_delegate' do
|
107
|
+
let(:other_name) { "foo" }
|
108
|
+
let(:other_attribute) { "other value" }
|
109
|
+
let(:other_record) { OtherModel.new(:name => other_name, :other_attribute => other_attribute)}
|
110
|
+
let(:other_resource) { OtherResource.new(other_record) }
|
111
|
+
|
112
|
+
let(:record) { SimpleModel.new(id: 105, name: "george xvi", other_name: other_name) }
|
113
|
+
|
114
|
+
subject(:resource) { SimpleResource.new(record) }
|
115
|
+
|
116
|
+
it 'delegates to the target' do
|
117
|
+
expect(record).to receive(:other_model).and_return(other_record)
|
118
|
+
expect(resource.other_attribute).to eq(other_attribute)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
context "memoized resource creation" do
|
124
|
+
let(:other_name) { "foo" }
|
125
|
+
let(:other_attribute) { "other value" }
|
126
|
+
let(:other_record) { OtherModel.new(:name => other_name, :other_attribute => other_attribute)}
|
127
|
+
let(:other_resource) { OtherResource.new(other_record) }
|
128
|
+
let(:record) { SimpleModel.new(id: 105, name: "george xvi", other_name: other_name) }
|
129
|
+
|
130
|
+
subject(:resource) { SimpleResource.new(record) }
|
131
|
+
|
132
|
+
it 'memoizes related resource creation' do
|
133
|
+
allow(record).to receive(:other_model).and_return(other_record)
|
134
|
+
expect(resource.other_resource).to be(SimpleResource.new(record).other_resource)
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
context ".wrap" do
|
141
|
+
it 'memoizes resource creation' do
|
142
|
+
expect(SimpleResource.wrap(record)).to be(SimpleResource.wrap(record))
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'works with nil resources, returning an empty set' do
|
146
|
+
wrapped_obj = SimpleResource.wrap(nil)
|
147
|
+
expect(wrapped_obj).to be_kind_of(Array)
|
148
|
+
expect(wrapped_obj.length).to be(0)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'works array with nil member, returning only existing records' do
|
152
|
+
wrapped_set = SimpleResource.wrap([nil, record])
|
153
|
+
expect(wrapped_set).to be_kind_of(Array)
|
154
|
+
expect(wrapped_set.length).to be(1)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'works with non-enumerable objects, that respond to collect' do
|
158
|
+
collectable = double("ArrayProxy", to_a: [record, record] )
|
159
|
+
|
160
|
+
wrapped_set = SimpleResource.wrap(collectable)
|
161
|
+
expect(wrapped_set.length).to be(2)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'works regardless of the resource class used' do
|
165
|
+
expect(SimpleResource.wrap(record)).to be(OtherResource.wrap(record))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|