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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +323 -324
  3. data/lib/praxis/action_definition.rb +7 -9
  4. data/lib/praxis/api_definition.rb +27 -44
  5. data/lib/praxis/api_general_info.rb +2 -3
  6. data/lib/praxis/application.rb +14 -141
  7. data/lib/praxis/bootloader.rb +1 -2
  8. data/lib/praxis/bootloader_stages/environment.rb +13 -0
  9. data/lib/praxis/controller.rb +0 -2
  10. data/lib/praxis/dispatcher.rb +4 -6
  11. data/lib/praxis/docs/generator.rb +8 -18
  12. data/lib/praxis/docs/link_builder.rb +1 -1
  13. data/lib/praxis/error_handler.rb +5 -5
  14. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +1 -1
  15. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
  16. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +16 -18
  17. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +5 -5
  18. data/lib/praxis/extensions/field_selection.rb +1 -12
  19. data/lib/praxis/extensions/rendering.rb +1 -1
  20. data/lib/praxis/file_group.rb +1 -1
  21. data/lib/praxis/handlers/xml.rb +1 -1
  22. data/lib/praxis/mapper/active_model_compat.rb +63 -0
  23. data/lib/praxis/mapper/resource.rb +242 -0
  24. data/lib/praxis/mapper/selector_generator.rb +126 -0
  25. data/lib/praxis/mapper/sequel_compat.rb +37 -0
  26. data/lib/praxis/middleware_app.rb +13 -15
  27. data/lib/praxis/multipart/part.rb +3 -5
  28. data/lib/praxis/plugins/mapper_plugin.rb +50 -0
  29. data/lib/praxis/request.rb +14 -7
  30. data/lib/praxis/request_stages/response.rb +2 -3
  31. data/lib/praxis/resource_definition.rb +10 -14
  32. data/lib/praxis/response.rb +6 -5
  33. data/lib/praxis/response_definition.rb +5 -7
  34. data/lib/praxis/response_template.rb +3 -4
  35. data/lib/praxis/responses/http.rb +36 -0
  36. data/lib/praxis/responses/internal_server_error.rb +12 -3
  37. data/lib/praxis/responses/multipart_ok.rb +11 -4
  38. data/lib/praxis/responses/validation_error.rb +10 -1
  39. data/lib/praxis/router.rb +3 -3
  40. data/lib/praxis/tasks/api_docs.rb +2 -10
  41. data/lib/praxis/tasks/routes.rb +0 -1
  42. data/lib/praxis/version.rb +1 -1
  43. data/lib/praxis.rb +13 -9
  44. data/praxis.gemspec +2 -3
  45. data/spec/functional_spec.rb +0 -1
  46. data/spec/praxis/action_definition_spec.rb +15 -26
  47. data/spec/praxis/api_definition_spec.rb +8 -13
  48. data/spec/praxis/api_general_info_spec.rb +8 -3
  49. data/spec/praxis/application_spec.rb +7 -13
  50. data/spec/praxis/handlers/xml_spec.rb +2 -2
  51. data/spec/praxis/mapper/resource_spec.rb +169 -0
  52. data/spec/praxis/mapper/selector_generator_spec.rb +301 -0
  53. data/spec/praxis/middleware_app_spec.rb +15 -9
  54. data/spec/praxis/request_spec.rb +7 -17
  55. data/spec/praxis/request_stages/validate_spec.rb +1 -1
  56. data/spec/praxis/resource_definition_spec.rb +10 -12
  57. data/spec/praxis/response_definition_spec.rb +5 -22
  58. data/spec/praxis/response_spec.rb +5 -12
  59. data/spec/praxis/responses/internal_server_error_spec.rb +5 -2
  60. data/spec/praxis/router_spec.rb +4 -8
  61. data/spec/spec_app/app/models/person.rb +3 -3
  62. data/spec/spec_app/config/environment.rb +3 -21
  63. data/spec/spec_app/config.ru +6 -1
  64. data/spec/spec_helper.rb +2 -17
  65. data/spec/support/spec_resources.rb +131 -0
  66. metadata +19 -31
  67. data/lib/praxis/extensions/attribute_filtering/query_builder.rb +0 -39
  68. data/lib/praxis/extensions/attribute_filtering.rb +0 -28
  69. data/lib/praxis/extensions/mapper_selectors.rb +0 -16
  70. data/lib/praxis/media_type_collection.rb +0 -127
  71. data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
  72. data/spec/praxis/media_type_collection_spec.rb +0 -157
  73. 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 = (application.versioning_scheme == :path)
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, application: application)
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 application.config.praxis.x_cascade
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
- apps = Praxis::Application.registered_apps
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
@@ -11,7 +11,6 @@ namespace :praxis do
11
11
  ]
12
12
 
13
13
  rows = []
14
- # TODO SINGLETON: ... what do do here?...
15
14
  Praxis::Application.instance.resource_definitions.each do |resource_definition|
16
15
  resource_definition.actions.each do |name, action|
17
16
  method = begin
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '0.22.pre.2'
2
+ VERSION = '2.0.pre.1'
3
3
  end
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 :AttributeFiltering, 'praxis/extensions/attribute_filtering'
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
- # # Avoid loading responses (and templates) lazily as they need to be registered in time
127
- # ... now done in Praxis initialize
128
- # require 'praxis/responses/http'
129
- # require 'praxis/responses/internal_server_error'
130
- # require 'praxis/responses/validation_error'
131
- # require 'praxis/responses/multipart_ok'
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', '~> 0.18'
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', '~> 0'
52
+ spec.add_development_dependency 'yard', ">= 0.9.20"
54
53
  spec.add_development_dependency 'coveralls'
55
54
  end
@@ -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
- praxis_instance.api_definition.define do |api|
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(praxis_instance.api_definition).to receive(:traits).and_return(traits)
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 '/pref/foobars/hello_world/test_trait/:app_name/:one' }
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
- subject(:action) do
330
- api_def=Praxis::ApiDefinition.new(praxis_instance)
331
- api_def.define do |api|
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
- # No setter...and its fine to do it here as it would not be used in a runtime situation
345
- praxis_instance.instance_variable_set(:@api_definition, api_def)
346
- # Define the action after the api_definition is set (as it uses is config to setup the routes)
347
- Praxis::ActionDefinition.new(:bar, resource_definition) do
348
- routing { get '/:one' }
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
- # TODO: perhaps grab the spec app instance from some global var...(instead of relying on the singleton compat method)
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(app_instance)
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 'initial definition' do
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(application: APP) }
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(application: APP) }
63
- subject(:info){ Praxis::ApiGeneralInfo.new(global_info, application: APP, version: '1.0') }
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).new(skip_registration: true)
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).new(skip_registration: true) }
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.new(skip_registration: true) }
98
- let(:boot_loader) { double("BL", setup!: true) }
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(:bootloader).and_return(boot_loader)
104
- allow(subject).to receive(:builder).and_return(builder)
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.new(100),0.1,true,Date.new] }
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.new(100),
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