intermodal 0.0.1 → 0.4.0

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 (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +1 -1
  5. data/README +6 -4
  6. data/Rakefile +6 -0
  7. data/intermodal.gemspec +51 -0
  8. data/lib/generators/intermodal_generator.rb +8 -0
  9. data/lib/intermodal.rb +122 -0
  10. data/lib/intermodal/api.rb +246 -0
  11. data/lib/intermodal/api/configuration.rb +40 -0
  12. data/lib/intermodal/api/railties.rb +21 -0
  13. data/lib/intermodal/concerns/acceptors/named_resource.rb +12 -0
  14. data/lib/intermodal/concerns/acceptors/resource.rb +12 -0
  15. data/lib/intermodal/concerns/controllers/accountability.rb +17 -0
  16. data/lib/intermodal/concerns/controllers/anonymous.rb +24 -0
  17. data/lib/intermodal/concerns/controllers/authenticatable.rb +24 -0
  18. data/lib/intermodal/concerns/controllers/paginated_collection.rb +25 -0
  19. data/lib/intermodal/concerns/controllers/presentation.rb +17 -0
  20. data/lib/intermodal/concerns/controllers/resource.rb +51 -0
  21. data/lib/intermodal/concerns/controllers/resource_linking.rb +58 -0
  22. data/lib/intermodal/concerns/let.rb +21 -0
  23. data/lib/intermodal/concerns/models/access_credential.rb +28 -0
  24. data/lib/intermodal/concerns/models/account.rb +16 -0
  25. data/lib/intermodal/concerns/models/accountability.rb +47 -0
  26. data/lib/intermodal/concerns/models/db_access_token.rb +29 -0
  27. data/lib/intermodal/concerns/models/has_parent_resource.rb +45 -0
  28. data/lib/intermodal/concerns/models/presentation.rb +25 -0
  29. data/lib/intermodal/concerns/models/redis_access_token.rb +60 -0
  30. data/lib/intermodal/concerns/models/resource_linking.rb +126 -0
  31. data/lib/intermodal/concerns/models/sanitize_html.rb +35 -0
  32. data/lib/intermodal/concerns/presenters/named_resource.rb +12 -0
  33. data/lib/intermodal/concerns/presenters/resource.rb +14 -0
  34. data/lib/intermodal/concerns/rails/rails_3_stack.rb +42 -0
  35. data/lib/intermodal/concerns/rails/rails_4_stack.rb +17 -0
  36. data/lib/intermodal/concerns/rails/use_warden.rb +21 -0
  37. data/lib/intermodal/config.rb +15 -0
  38. data/lib/intermodal/configuration.rb +11 -0
  39. data/lib/intermodal/controllers/api_controller.rb +26 -0
  40. data/lib/intermodal/controllers/linking_resource_controller.rb +8 -0
  41. data/lib/intermodal/controllers/nested_resource_controller.rb +18 -0
  42. data/lib/intermodal/controllers/resource_controller.rb +11 -0
  43. data/lib/intermodal/dsl/controllers.rb +125 -0
  44. data/lib/intermodal/dsl/mapping.rb +79 -0
  45. data/lib/intermodal/dsl/presentation_helpers.rb +107 -0
  46. data/lib/intermodal/mapping/acceptor.rb +2 -2
  47. data/lib/intermodal/mapping/mapper.rb +39 -13
  48. data/lib/intermodal/mapping/presenter.rb +12 -6
  49. data/lib/intermodal/proxies/linking_resources.rb +58 -0
  50. data/lib/intermodal/proxies/will_paginate.rb +85 -0
  51. data/lib/intermodal/rack/auth.rb +29 -0
  52. data/lib/intermodal/rack/dummy_store.rb +24 -0
  53. data/lib/intermodal/rack/rescue.rb +82 -0
  54. data/lib/intermodal/responders/linking_resource_responder.rb +21 -0
  55. data/lib/intermodal/responders/resource_responder.rb +64 -0
  56. data/lib/intermodal/rspec/acceptors.rb +79 -0
  57. data/lib/intermodal/rspec/models/accountability.rb +114 -0
  58. data/lib/intermodal/rspec/models/has_parent_resource.rb +132 -0
  59. data/lib/intermodal/rspec/models/resource_linking.rb +234 -0
  60. data/lib/intermodal/rspec/models/sanitization.rb +84 -0
  61. data/lib/intermodal/rspec/presenters.rb +92 -0
  62. data/lib/intermodal/rspec/requests/authenticated_requests.rb +17 -0
  63. data/lib/intermodal/rspec/requests/linked_resources.rb +180 -0
  64. data/lib/intermodal/rspec/requests/paginated_collection.rb +60 -0
  65. data/lib/intermodal/rspec/requests/rack.rb +142 -0
  66. data/lib/intermodal/rspec/requests/request_validations.rb +36 -0
  67. data/lib/intermodal/rspec/requests/resources.rb +275 -0
  68. data/lib/intermodal/rspec/requests/rfc2616_status_codes.rb +51 -0
  69. data/lib/intermodal/rspec/validators.rb +86 -0
  70. data/lib/intermodal/validators/account_validator.rb +27 -0
  71. data/lib/intermodal/validators/different_account_validator.rb +27 -0
  72. data/lib/intermodal/version.rb +3 -0
  73. data/spec/mapping/acceptors_spec.rb +142 -0
  74. data/spec/mapping/presenters_spec.rb +186 -0
  75. data/spec/models/accountability_spec.rb +13 -0
  76. data/spec/models/has_parent_resource_spec.rb +18 -0
  77. data/spec/models/resource_linking_spec.rb +21 -0
  78. data/spec/proxies/will_paginate_spec.rb +163 -0
  79. data/spec/rack/auth_spec.rb +51 -0
  80. data/spec/requests/linked_resources.rb +37 -0
  81. data/spec/requests/nested_resources_spec.rb +54 -0
  82. data/spec/requests/resources_spec.rb +50 -0
  83. data/spec/spec_helper.rb +53 -0
  84. data/spec/support/api.rb +50 -0
  85. data/spec/support/app/class_builder.rb +41 -0
  86. data/spec/support/app/db/adapter_helper.rb +53 -0
  87. data/spec/support/app/db/authentication_schema_helper.rb +62 -0
  88. data/spec/support/app/db/migration_helper.rb +44 -0
  89. data/spec/support/app/schema.rb +101 -0
  90. data/spec/support/application.rb +23 -0
  91. data/spec/support/blueprints.rb +41 -0
  92. data/spec/support/epiphyte.rb +29 -0
  93. metadata +393 -52
  94. data/lib/intermodal/base.rb +0 -13
  95. data/lib/intermodal/declare_controllers.rb +0 -102
  96. data/lib/intermodal/mapping.rb +0 -4
  97. data/lib/intermodal/mapping/dsl.rb +0 -76
@@ -0,0 +1,27 @@
1
+ require 'active_model/validator'
2
+
3
+ module Intermodal
4
+ module Validators
5
+ # Validates the associated resource is owned by the same account
6
+ # Examples:
7
+ #
8
+ # validates parent_id, account: true
9
+ # validates parent_id, account: { through: association }
10
+ #
11
+ class AccountValidator < ActiveModel::EachValidator
12
+ def validate_each(record, attribute, value)
13
+ return if value.nil?
14
+ association_name = options[:through] || attribute.to_s.gsub(/_id/, '')
15
+ return if record.send(association_name).account_id == record.account_id
16
+ record.errors[attribute] << message
17
+ end
18
+
19
+ def message
20
+ options[:message] || 'must belong to the same account'
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+
27
+ AccountValidator = Intermodal::Validators::AccountValidator unless defined? AccountValidator
@@ -0,0 +1,27 @@
1
+ require 'active_model/validator'
2
+
3
+ module Intermodal
4
+ module Validators
5
+ # Validates the associated resource is owned by a different account
6
+ # Examples:
7
+ #
8
+ # validates parent_id, different_account: true
9
+ # validates parent_id, different_account: { through: association }
10
+ #
11
+ class DifferentAccountValidator < ActiveModel::EachValidator
12
+ def validate_each(record, attribute, value)
13
+ return if value.nil?
14
+ association_name = options[:through] || attribute.to_s.gsub(/_id/, '')
15
+ return if record.send(association_name).account_id != record.account_id
16
+ record.errors[attribute] << message
17
+ end
18
+
19
+ def message
20
+ options[:message] || 'must belong to a different account'
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+
27
+ DifferentAccountValidator = Intermodal::Validators::DifferentAccountValidator unless defined? DifferentAccountValidator
@@ -0,0 +1,3 @@
1
+ module Intermodal
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1,142 @@
1
+ require 'spec_helper'
2
+
3
+ describe Intermodal::Mapping::Acceptor do
4
+ include SpecHelpers::ClassBuilder
5
+
6
+ describe '.call' do
7
+ subject { acceptor.call(params) }
8
+
9
+ context 'with pass-through mappings' do
10
+ let(:acceptor) do
11
+ define_class :TestAcceptor, Intermodal::Mapping::Acceptor do
12
+ accepts :acceptable_field
13
+ accepts :optional_field
14
+ end
15
+ end
16
+
17
+ let(:value) { 'Accepted Field' }
18
+ let(:params) { { :acceptable_field => value, :unacceptable_field => true } }
19
+
20
+ it 'should accept explicitly whitelisted fields' do
21
+ should include(:acceptable_field)
22
+ end
23
+
24
+ it 'should pass through value without transformation' do
25
+ subject[:acceptable_field].should eql(value)
26
+ end
27
+
28
+ it 'should not accept undeclared fields' do
29
+ should_not include(:unacceptable_field)
30
+ end
31
+
32
+ context 'without optional field in params' do
33
+ # Since we pass the output of an acceptor directly into
34
+ # the active record #create or #update_attributes, we want
35
+ # the hash to look like:
36
+ #
37
+ # { :acceptable_fields => 'Something' }
38
+ #
39
+ # rather than
40
+ #
41
+ # { :acceptable_field => 'Something', :optional_field => nil }
42
+ #
43
+ # Doing so will update the resource and null out the fields that
44
+ # have not been passed through params.
45
+ it 'should not include the optional field' do
46
+ should_not include(:optional_field)
47
+ end
48
+ end
49
+
50
+ context 'with optional field in params' do
51
+ let(:params) { { :acceptable_field => value, :optional_field => value } }
52
+
53
+ it 'should include the optional field' do
54
+ should include(:optional_field)
55
+ end
56
+
57
+ it 'should pass through value without transformation' do
58
+ subject[:optional_field].should eql(value)
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'with pass-through alias mappings' do
64
+ let(:acceptor) do
65
+ define_class :TestAcceptor, Intermodal::Mapping::Acceptor do
66
+ accepts :name, :with => :full_name
67
+ end
68
+ end
69
+
70
+ let(:value) { 'Henry Deacon' }
71
+ let(:params) { { :full_name => value } }
72
+
73
+ it 'should present explicitly whitelisted fields' do
74
+ should include(:name)
75
+ end
76
+
77
+ it 'should pass through value without transformation' do
78
+ subject[:name].should eql(params[:full_name])
79
+ end
80
+
81
+ it 'should not present undeclared fields' do
82
+ should_not include(:full_name)
83
+ end
84
+ end
85
+
86
+ context 'with nested mappings' do
87
+ let(:acceptor) do
88
+ define_class :TestAcceptor, Intermodal::Mapping::Acceptor do
89
+ # Use this to create nested parameters from flattened parameters
90
+ accepts :name, :with => [ :first_name, :last_name ]
91
+ end
92
+ end
93
+
94
+ let(:first_name) { 'Henry' }
95
+ let(:last_name) { 'Deacon' }
96
+ let(:params) { { :first_name => first_name, :last_name => last_name } }
97
+
98
+ it 'should merge fields into a single hash of fields' do
99
+ should include(:name)
100
+ subject[:name].should include(:first_name)
101
+ subject[:name].should include(:last_name)
102
+ end
103
+
104
+ it 'should merge fields without transforming its value' do
105
+ subject[:name][:first_name].should eql(first_name)
106
+ subject[:name][:last_name].should eql(last_name)
107
+ end
108
+
109
+ # Unless you've explicitly declared the merged fields, it should
110
+ # not include it.
111
+ it 'should not accept merged fields' do
112
+ should_not include(:first_name)
113
+ should_not include(:last_name)
114
+ end
115
+ end
116
+
117
+ context 'with arbitrary mappings' do
118
+ let(:acceptor) do
119
+ define_class :TestAcceptor, Intermodal::Mapping::Acceptor do
120
+ # This lets you use aribrary functions to map out accepted fields.
121
+ # Beware! For many use-cases, declaring these in the model works better.
122
+ # Beware! Do not use functions with side-effects
123
+ accepts :phone_number, :with => lambda { |o| o[:phone_number].gsub(/[^0-9]/, '') }
124
+ end
125
+ end
126
+
127
+ let(:value) { '555-555-5555' }
128
+ let(:params) { { :phone_number => value } }
129
+
130
+ it 'should accept an arbitrary mapping' do
131
+ should include(:phone_number)
132
+ end
133
+
134
+ it 'should transform the value' do
135
+ subject[:phone_number].should_not eql(value)
136
+ end
137
+ end
138
+
139
+ pending 'with scoped acceptor'
140
+ end
141
+ end
142
+
@@ -0,0 +1,186 @@
1
+ require 'spec_helper'
2
+
3
+ describe Intermodal::Mapping::Presenter do
4
+ include SpecHelpers::ClassBuilder
5
+ include SpecHelpers::Application
6
+
7
+ describe '.call' do
8
+ subject { presenter.call(resource) }
9
+ let(:resource) { item }
10
+
11
+ context 'with pass-through mappings' do
12
+ let(:presenter) do
13
+ define_class :TestPresenter, Intermodal::Mapping::Presenter do
14
+ presents :name
15
+ presents :description
16
+ end
17
+ end
18
+
19
+ it 'should present explicitly whitelisted fields' do
20
+ should include(:name)
21
+ end
22
+
23
+ it 'should pass through value without transformation' do
24
+ subject[:name].should eql(resource.name)
25
+ end
26
+
27
+ it 'should present all declared fields regardless of value' do
28
+ should include(:description)
29
+ subject[:description].should be_nil
30
+ end
31
+ end
32
+
33
+ context 'with pass-through alias mappings' do
34
+ let(:presenter) do
35
+ define_class :TestPresenter, Intermodal::Mapping::Presenter do
36
+ presents :full_name, :with => :name
37
+ end
38
+ end
39
+
40
+ it 'should present explicitly whitelisted fields' do
41
+ should include(:full_name)
42
+ end
43
+
44
+ it 'should pass through value without transformation' do
45
+ subject[:full_name].should eql(resource.name)
46
+ end
47
+
48
+ it 'should not present undeclared fields' do
49
+ should_not include(:name)
50
+ end
51
+ end
52
+
53
+ context 'with nested mappings' do
54
+ let(:presenter) do
55
+ define_class :TestPresenter, Intermodal::Mapping::Presenter do
56
+
57
+ # Use this to create nested presentation
58
+ presents :metadata, :with => [ :name, :description ]
59
+ end
60
+ end
61
+
62
+ let(:resource) { Item.make(:name => name, :description => description) }
63
+ let(:name) { 'Weird Eureka Thing' }
64
+ let(:description) { 'Makes Eureka Go Boom!' }
65
+
66
+ it 'should merge fields into a single hash of fields' do
67
+ should include(:metadata)
68
+ subject[:metadata].should include(:name)
69
+ subject[:metadata].should include(:description)
70
+ end
71
+
72
+ it 'should merge fields without transforming its value' do
73
+ subject[:metadata][:name].should eql(name)
74
+ subject[:metadata][:description].should eql(description)
75
+ end
76
+
77
+ # Unless you've explicitly declared the merged fields, it should
78
+ # not present it.
79
+ it 'should not present undeclared fields' do
80
+ should_not include(:name)
81
+ should_not include(:description)
82
+ end
83
+ end
84
+
85
+ pending 'with always_nest_collection'
86
+ context 'with arbitrary mappings' do
87
+ let(:presenter) do
88
+ define_class :TestPresenter, Intermodal::Mapping::Presenter do
89
+ # This lets you use aribrary functions to map out accepted fields.
90
+ # Beware! For many use-cases, declaring these in the model works better.
91
+ # Beware! Do not use functions with side-effects
92
+ presents :name, :with => lambda { |o| o[:name].upcase }
93
+ end
94
+ end
95
+
96
+ it 'should accept an arbitrary mapping' do
97
+ should include(:name)
98
+ end
99
+
100
+ it 'should transform the value' do
101
+ subject[:name].should_not eql(resource.name)
102
+ end
103
+ end
104
+
105
+ context 'when scoping' do
106
+ subject { presenter.call(resource, :scope => scope) }
107
+
108
+ context 'without scope declaration' do
109
+ let(:scope) { :default }
110
+ let(:presenter) do
111
+ define_class :TestPresenter, Intermodal::Mapping::Presenter do
112
+ presents :name
113
+ end
114
+ end
115
+
116
+ it 'should default to :default scope' do
117
+ should include(:name)
118
+ subject[:name].should eql(resource.name)
119
+ end
120
+ end
121
+
122
+ context 'with mixed scopes' do
123
+ let(:scope) { :default }
124
+ let(:presenter) do
125
+ define_class :TestPresenter, Intermodal::Mapping::Presenter do
126
+ presents :name
127
+ presents :description, :scope => :details
128
+ end
129
+ end
130
+
131
+ let(:resource) { Item.make(:name => name, :description => description) }
132
+ let(:name) { 'Weird Eureka Thing' }
133
+ let(:description) { 'Makes Eureka Go Boom!' }
134
+
135
+ it 'should default to :default scope' do
136
+ should include(:name)
137
+ subject[:name].should eql(resource.name)
138
+ end
139
+
140
+ it 'should not present attributes outside of scope' do
141
+ should_not include(:description)
142
+ end
143
+
144
+ context 'when called with non-default scope' do
145
+ let(:scope) { :details }
146
+
147
+ it 'should include non-overlapping attributes from default scope' do
148
+ should include(:name)
149
+ subject[:name].should eql(resource.name)
150
+ end
151
+
152
+ it 'should present attributes from non-default scope' do
153
+ should include(:description)
154
+ subject[:description].should eql(resource.description)
155
+ end
156
+ end
157
+ end
158
+
159
+ context 'with overlapping scopes' do
160
+ let(:scope) { :default }
161
+ let(:presenter) do
162
+ define_class :TestPresenter, Intermodal::Mapping::Presenter do
163
+ presents :name
164
+ presents :name, :with => lambda { |o| 'boo!' }, :scope => :details
165
+ end
166
+ end
167
+
168
+ it 'should default to :default scope' do
169
+ should include(:name)
170
+ subject[:name].should eql(resource.name)
171
+ end
172
+
173
+ context 'when called with non-default scope' do
174
+ let(:scope) { :details }
175
+
176
+ it 'should present attributes from non-default scope' do
177
+ should include(:name)
178
+ subject[:name].should_not eql(resource.name)
179
+ subject[:name].should eql('boo!')
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Intermodal::Models::Accountability do
4
+ context 'when implemented as a resource' do
5
+ include SpecHelpers::Application
6
+ include Intermodal::RSpec::Accountability
7
+
8
+ subject { item }
9
+ implements_get_interface
10
+
11
+ pending 'should belong to account model'
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Intermodal::Models::HasParentResource do
4
+ context 'when implemented as a resource' do
5
+ include SpecHelpers::Application
6
+ include Intermodal::RSpec::HasParentResource
7
+
8
+ subject { part }
9
+ let(:parent_resource_name) { :item }
10
+ let(:different_parent) { Item.make!(:account => account) }
11
+ let(:parent_with_different_account) { Item.make!(:account => different_account) }
12
+ let(:different_account) { Account.make! }
13
+
14
+ implements_get_interface_for_nested_resource
15
+
16
+ pending 'should belong to parent model'
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Intermodal::Models::ResourceLinking do
4
+ context 'when implemented as a resource' do
5
+ include SpecHelpers::Application
6
+ include Intermodal::RSpec::ResourceLinking
7
+
8
+ subject { sku }
9
+
10
+ let(:model) { Sku }
11
+ let(:skus) { item.vendors << vendors }
12
+
13
+ # Protip: sku, item, and vendors can be defined in a global helper, such as SpecHelpers::Application
14
+ let(:sku) { Sku.create(:item => item, :vendor => vendor) }
15
+ let(:item) { Item.make!(:account => account) }
16
+ let(:vendor) { Vendor.make!(:account => account) }
17
+ let(:vendors) { (1..3).map { Vendor.make!(:account => account) } }
18
+
19
+ concerned_with_resource_linking :item, :vendor, :skip_association_examples => true, :skip_validation_examples => true
20
+ end
21
+ end
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+
3
+ describe Intermodal::Proxies::WillPaginate::Collection do
4
+ include Intermodal::RSpec::Resources
5
+ include SpecHelpers::Application
6
+ include SpecHelpers::API
7
+
8
+ let(:api_class) do
9
+ define_class :Api, Rails::Engine do
10
+ include Intermodal::API
11
+ include SpecHelpers::Epiphyte
12
+
13
+ map_data do
14
+ presentation_for :item do
15
+ presents :id
16
+ presents :name
17
+ presents :description
18
+ end
19
+ end
20
+
21
+ controllers do
22
+ end
23
+
24
+ routes.draw do
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '#as_json' do
30
+ before(:each) { api_class.load_presentations! }
31
+ subject { presented_collection }
32
+
33
+ let(:presenter) { api_class.presenters[:item] }
34
+ let(:collection) { Item.make!(3, :account => account) }
35
+ let(:paginated_collection) { collection; Item.paginate :page => 1 }
36
+ let(:presented_collection) { paginated_collection.as_json presenter_options }
37
+
38
+ let(:collection_ids) { collection.map(&:id) }
39
+
40
+ let(:_ids) { ->(r) { r[:id] } }
41
+ let(:presented_collection_ids) { presented_collection[:collection].map(&_ids) }
42
+
43
+ context 'with presenter default options' do
44
+ let(:presenter_options) { { :presenter => presenter } }
45
+
46
+ it 'should present page' do
47
+ subject[:page].should_not be_nil
48
+ end
49
+
50
+ it 'should present per_page' do
51
+ subject[:per_page].should_not be_nil
52
+ end
53
+
54
+ it 'should present total_pages' do
55
+ subject[:total_pages].should_not be_nil
56
+ end
57
+
58
+ it 'should present total_entries' do
59
+ subject[:total_entries].should_not be_nil
60
+ end
61
+
62
+ it 'should present collection' do
63
+ subject[:collection].should_not be_nil
64
+ end
65
+
66
+ it 'should present collection of resources' do
67
+ presented_collection_ids.should eql(collection_ids)
68
+ end
69
+ end
70
+
71
+ context 'with root option' do
72
+ let(:presenter_options) { { :presenter => presenter, :root => root } }
73
+ let(:root) { :node }
74
+ let(:presented_collection_ids) { presented_collection[root].map(&_ids) }
75
+
76
+ it 'should present page' do
77
+ subject[:page].should_not be_nil
78
+ end
79
+
80
+ it 'should present per_page' do
81
+ subject[:per_page].should_not be_nil
82
+ end
83
+
84
+ it 'should present total_pages' do
85
+ subject[:total_pages].should_not be_nil
86
+ end
87
+
88
+ it 'should present total_entries' do
89
+ subject[:total_entries].should_not be_nil
90
+ end
91
+
92
+ it 'should present collection' do
93
+ subject[root].should_not be_nil
94
+ end
95
+
96
+ it 'should present collection of resources' do
97
+ presented_collection_ids.should eql(collection_ids)
98
+ end
99
+ end
100
+
101
+ context 'with always_nest_collections option' do
102
+ let(:presenter_options) { { :presenter => presenter, :always_nest_collections => true } }
103
+ let(:presented_collection_ids) { presented_collection[:collection].map(&_ids) }
104
+ let(:_ids) { ->(r) { r['item'][:id] } }
105
+
106
+ it 'should present page' do
107
+ subject[:page].should_not be_nil
108
+ end
109
+
110
+ it 'should present per_page' do
111
+ subject[:per_page].should_not be_nil
112
+ end
113
+
114
+ it 'should present total_pages' do
115
+ subject[:total_pages].should_not be_nil
116
+ end
117
+
118
+ it 'should present total_entries' do
119
+ subject[:total_entries].should_not be_nil
120
+ end
121
+
122
+ it 'should present collection' do
123
+ subject[:collection].should_not be_nil
124
+ end
125
+
126
+ it 'should present collection of resources' do
127
+ presented_collection_ids.should eql(collection_ids)
128
+ end
129
+ end
130
+
131
+ context 'with always_nest_collections and root option' do
132
+ let(:presenter_options) { { :presenter => presenter, :root => root, :always_nest_collections => true } }
133
+ let(:root) { :nodes }
134
+ let(:element_name) { root.to_s.singularize }
135
+ let(:presented_collection_ids) { presented_collection[root].map(&_ids) }
136
+ let(:_ids) { ->(r) { r[element_name][:id] } }
137
+
138
+ it 'should present page' do
139
+ subject[:page].should_not be_nil
140
+ end
141
+
142
+ it 'should present per_page' do
143
+ subject[:per_page].should_not be_nil
144
+ end
145
+
146
+ it 'should present total_pages' do
147
+ subject[:total_pages].should_not be_nil
148
+ end
149
+
150
+ it 'should present total_entries' do
151
+ subject[:total_entries].should_not be_nil
152
+ end
153
+
154
+ it 'should present collection' do
155
+ subject[root].should_not be_nil
156
+ end
157
+
158
+ it 'should present collection of resources' do
159
+ presented_collection_ids.should eql(collection_ids)
160
+ end
161
+ end
162
+ end
163
+ end