praxis 0.18.1 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -1
- data/Gemfile +2 -1
- data/README.md +21 -27
- data/lib/api_browser/app/index.html +3 -3
- data/lib/api_browser/app/js/app.js +23 -3
- data/lib/api_browser/app/js/controllers/action.js +33 -21
- data/lib/api_browser/app/js/controllers/controller.js +3 -25
- data/lib/api_browser/app/js/controllers/menu.js +61 -51
- data/lib/api_browser/app/js/controllers/trait.js +10 -0
- data/lib/api_browser/app/js/controllers/type.js +8 -5
- data/lib/api_browser/app/js/directives/fixed_if_fits.js +9 -2
- data/lib/api_browser/app/js/directives/menu_item.js +59 -0
- data/lib/api_browser/app/js/directives/readable_list.js +87 -0
- data/lib/api_browser/app/js/directives/url.js +16 -0
- data/lib/api_browser/app/js/factories/Configuration.js +1 -2
- data/lib/api_browser/app/js/factories/Documentation.js +49 -7
- data/lib/api_browser/app/js/factories/PageInfo.js +9 -0
- data/lib/api_browser/app/js/factories/normalize_attributes.js +1 -2
- data/lib/api_browser/app/js/factories/template_for.js +9 -4
- data/lib/api_browser/app/sass/modules/_sidebar.scss +54 -15
- data/lib/api_browser/app/sass/praxis.scss +4 -0
- data/lib/api_browser/app/views/action.html +72 -41
- data/lib/api_browser/app/views/builtin/field-selector.html +24 -0
- data/lib/api_browser/app/views/controller.html +9 -10
- data/lib/api_browser/app/views/directives/menu_item.html +8 -0
- data/lib/api_browser/app/views/directives/url.html +3 -0
- data/lib/api_browser/app/views/layout.html +2 -2
- data/lib/api_browser/app/views/menu.html +8 -14
- data/lib/api_browser/app/views/navbar.html +1 -1
- data/lib/api_browser/app/views/trait.html +13 -0
- data/lib/api_browser/app/views/type/details.html +1 -1
- data/lib/api_browser/app/views/type.html +1 -1
- data/lib/api_browser/app/views/types/embedded/field-selector.html +13 -0
- data/lib/api_browser/app/views/types/label/primitive.html +1 -1
- data/lib/api_browser/app/views/types/standalone/array.html +3 -0
- data/lib/praxis/action_definition.rb +15 -2
- data/lib/praxis/collection.rb +17 -5
- data/lib/praxis/controller.rb +12 -3
- data/lib/praxis/docs/generator.rb +11 -7
- data/lib/praxis/extensions/field_expansion.rb +59 -0
- data/lib/praxis/extensions/field_selection/field_selector.rb +125 -0
- data/lib/praxis/extensions/field_selection.rb +10 -0
- data/lib/praxis/extensions/mapper_selectors.rb +16 -0
- data/lib/praxis/extensions/rendering.rb +43 -0
- data/lib/praxis/links.rb +1 -0
- data/lib/praxis/media_type.rb +87 -3
- data/lib/praxis/media_type_collection.rb +1 -1
- data/lib/praxis/media_type_identifier.rb +6 -1
- data/lib/praxis/plugins/praxis_mapper_plugin.rb +29 -10
- data/lib/praxis/restful_doc_generator.rb +11 -8
- data/lib/praxis/tasks/api_docs.rb +6 -5
- data/lib/praxis/types/multipart_array.rb +1 -1
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +5 -0
- data/praxis.gemspec +4 -3
- data/spec/api_browser/factories/configuration_spec.js +32 -0
- data/spec/api_browser/factories/documentation_spec.js +75 -25
- data/spec/api_browser/factories/normalize_attributes_spec.js +0 -5
- data/spec/praxis/{types/collection_spec.rb → collection_spec.rb} +36 -23
- data/spec/praxis/extensions/field_expansion_spec.rb +96 -0
- data/spec/praxis/extensions/field_selection/field_selector_spec.rb +92 -0
- data/spec/praxis/extensions/rendering_spec.rb +63 -0
- data/spec/praxis/links_spec.rb +6 -0
- data/spec/praxis/media_type_collection_spec.rb +0 -1
- data/spec/praxis/media_type_identifier_spec.rb +15 -1
- data/spec/praxis/media_type_spec.rb +101 -3
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +33 -24
- data/spec/praxis/request_stages/request_stage_spec.rb +1 -1
- data/spec/praxis/types/multipart_array_spec.rb +14 -4
- data/spec/spec_app/app/controllers/instances.rb +6 -1
- data/spec/spec_app/config/environment.rb +2 -1
- data/spec/spec_app/design/resources/instances.rb +1 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/support/spec_media_types.rb +224 -1
- metadata +50 -16
@@ -36,7 +36,7 @@ require 'terminal-table'
|
|
36
36
|
# output into the Praxis::Application.instance.logger app log. Possible
|
37
37
|
# values are: "detailed", "short", and "skip" (i.e. do not print the stats
|
38
38
|
# at all).
|
39
|
-
# 3. stats_log_level: the logging level with which the statistics should be logged.
|
39
|
+
# 3. stats_log_level: the logging level with which the statistics should be logged.
|
40
40
|
#
|
41
41
|
# See http://praxis-framework.io/reference/plugins/ for further details on how
|
42
42
|
# to use a plugin and pass it options.
|
@@ -76,7 +76,7 @@ module Praxis
|
|
76
76
|
def prepare_config!(node)
|
77
77
|
node.attributes do
|
78
78
|
attribute :log_stats, String, values: ['detailed', 'short', 'skip'], default: 'detailed'
|
79
|
-
attribute :stats_log_level, Symbol, values: [:fatal,:error,:warn,:info,:debug], default: :info
|
79
|
+
attribute :stats_log_level, Symbol, values: [:fatal,:error,:warn,:info,:debug], default: :info
|
80
80
|
attribute :repositories, Attributor::Hash.of(key: String, value: RepositoryConfig)
|
81
81
|
end
|
82
82
|
end
|
@@ -104,7 +104,8 @@ module Praxis
|
|
104
104
|
log_stats = PraxisMapperPlugin::Plugin.instance.config.log_stats
|
105
105
|
unless log_stats == 'skip'
|
106
106
|
Praxis::Notifications.subscribe 'praxis.request.all' do |name, *junk, payload|
|
107
|
-
if (
|
107
|
+
if (payload[:request].identity_map?)
|
108
|
+
identity_map = payload[:request].identity_map
|
108
109
|
PraxisMapperPlugin::Statistics.log(payload[:request], identity_map, log_stats)
|
109
110
|
end
|
110
111
|
end
|
@@ -122,13 +123,17 @@ module Praxis
|
|
122
123
|
|
123
124
|
module Request
|
124
125
|
def identity_map
|
125
|
-
@identity_map
|
126
|
+
@identity_map ||= Praxis::Mapper::IdentityMap.new
|
126
127
|
end
|
127
128
|
|
128
129
|
def identity_map=(map)
|
129
130
|
@identity_map = map
|
130
131
|
end
|
131
|
-
|
132
|
+
|
133
|
+
def identity_map?
|
134
|
+
!@identity_map.nil?
|
135
|
+
end
|
136
|
+
|
132
137
|
def silence_mapper_stats
|
133
138
|
@silence_mapper_stats
|
134
139
|
end
|
@@ -143,10 +148,24 @@ module Praxis
|
|
143
148
|
extend ActiveSupport::Concern
|
144
149
|
|
145
150
|
included do
|
146
|
-
|
147
|
-
|
151
|
+
# Ensure we call #release on any identity map
|
152
|
+
# that may be set by the controller after the action
|
153
|
+
# completes.
|
154
|
+
around :action do |controller, callee|
|
155
|
+
begin
|
156
|
+
callee.call
|
157
|
+
ensure
|
158
|
+
if controller.request.identity_map?
|
159
|
+
controller.request.identity_map.release
|
160
|
+
end
|
161
|
+
end
|
148
162
|
end
|
149
163
|
end
|
164
|
+
|
165
|
+
def identity_map
|
166
|
+
request.identity_map
|
167
|
+
end
|
168
|
+
|
150
169
|
end
|
151
170
|
|
152
171
|
module Statistics
|
@@ -158,8 +177,8 @@ module Praxis
|
|
158
177
|
self.to_logger "No database interactions observed."
|
159
178
|
return
|
160
179
|
end
|
161
|
-
|
162
|
-
|
180
|
+
|
181
|
+
|
163
182
|
case log_stats
|
164
183
|
when 'detailed'
|
165
184
|
self.detailed(identity_map)
|
@@ -217,7 +236,7 @@ module Praxis
|
|
217
236
|
def self.short(identity_map)
|
218
237
|
self.to_logger identity_map.query_statistics.sum_totals.to_s
|
219
238
|
end
|
220
|
-
|
239
|
+
|
221
240
|
def self.to_logger(message)
|
222
241
|
Praxis::Application.instance.logger.__send__(Plugin.instance.config.stats_log_level, "Praxis::Mapper Statistics: #{message}")
|
223
242
|
end
|
@@ -17,7 +17,8 @@ module Praxis
|
|
17
17
|
Attributor::Ids,
|
18
18
|
Attributor::Integer,
|
19
19
|
Attributor::Object,
|
20
|
-
Attributor::String
|
20
|
+
Attributor::String,
|
21
|
+
Attributor::Symbol
|
21
22
|
]).freeze
|
22
23
|
|
23
24
|
def self.inspect_attributes(the_type)
|
@@ -33,7 +34,7 @@ module Praxis
|
|
33
34
|
if the_type.member_attribute.nil?
|
34
35
|
the_type = the_type.member_type
|
35
36
|
else
|
36
|
-
the_type = the_type.member_attribute.type
|
37
|
+
the_type = the_type.member_attribute.type
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
@@ -74,9 +75,6 @@ module Praxis
|
|
74
75
|
# Collect reachable types from the media_type if any (plus itself)
|
75
76
|
if @media_type && ! @media_type.is_a?(Praxis::SimpleMediaType)
|
76
77
|
add_to_reachable RestfulDocGenerator.inspect_attributes(@media_type)
|
77
|
-
@media_type.attributes.each do |name, attr|
|
78
|
-
add_to_reachable RestfulDocGenerator.inspect_attributes(attr)
|
79
|
-
end
|
80
78
|
@generated_example = @media_type.example(self.id)
|
81
79
|
end
|
82
80
|
|
@@ -133,6 +131,10 @@ module Praxis
|
|
133
131
|
return if found == nil
|
134
132
|
@reachable_types += found
|
135
133
|
end
|
134
|
+
|
135
|
+
def display_name
|
136
|
+
@controller_config.display_name
|
137
|
+
end
|
136
138
|
end
|
137
139
|
|
138
140
|
def initialize(root_dir)
|
@@ -141,7 +143,7 @@ module Praxis
|
|
141
143
|
@resources = []
|
142
144
|
|
143
145
|
Attributor::AttributeResolver.current = Attributor::AttributeResolver.new
|
144
|
-
|
146
|
+
|
145
147
|
remove_previous_doc_data
|
146
148
|
load_resources
|
147
149
|
|
@@ -276,7 +278,7 @@ module Praxis
|
|
276
278
|
info[:media_type] = r.media_type.id
|
277
279
|
media_types_seen_from_controllers << r.media_type
|
278
280
|
end
|
279
|
-
display_name = r.name.split("::").last
|
281
|
+
display_name = r.display_name #.name.split("::").last
|
280
282
|
index[r.version][display_name] = info
|
281
283
|
end
|
282
284
|
|
@@ -288,7 +290,8 @@ module Praxis
|
|
288
290
|
|
289
291
|
reportable_types.each do |type|
|
290
292
|
index[version] ||= Hash.new
|
291
|
-
|
293
|
+
next unless type.respond_to?(:display_name)
|
294
|
+
display_name = type.display_name #.name.split("::").last
|
292
295
|
if index[version].has_key? display_name
|
293
296
|
raise "Display name already taken for version #{version}! #{display_name}"
|
294
297
|
end
|
@@ -37,16 +37,17 @@ namespace :praxis do
|
|
37
37
|
exec({'USER_DOCS_PATH' => File.join(Dir.pwd, 'docs')}, "#{path}/node_modules/.bin/grunt build --gruntfile '#{path}/Gruntfile.js'")
|
38
38
|
end
|
39
39
|
|
40
|
-
desc "Generate API docs (JSON definitions) for a Praxis App"
|
41
|
-
task :
|
40
|
+
desc "Generate deprecated API docs (JSON definitions) for a Praxis App"
|
41
|
+
task :generate_old => [:environment] do |t, args|
|
42
42
|
require 'fileutils'
|
43
|
+
STDERR.puts "DEPRECATION: praxis:docs:generate_old is deprecated and will be removed in the next version. Please update tooling that may need this."
|
43
44
|
|
44
45
|
Praxis::Blueprint.caching_enabled = false
|
45
46
|
generator = Praxis::RestfulDocGenerator.new(Dir.pwd)
|
46
47
|
end
|
47
48
|
|
48
|
-
desc "Generate
|
49
|
-
task :
|
49
|
+
desc "Generate API docs (JSON definitions) for a Praxis App"
|
50
|
+
task :generate => [:environment] do |t, args|
|
50
51
|
require 'fileutils'
|
51
52
|
|
52
53
|
Praxis::Blueprint.caching_enabled = false
|
@@ -59,7 +60,7 @@ namespace :praxis do
|
|
59
60
|
desc "Generate API docs (JSON definitions) for a Praxis App"
|
60
61
|
task :api_docs do
|
61
62
|
STDERR.puts "DEPRECATION: praxis:api_docs is deprecated and will be removed by 1.0. Please use praxis:docs:generate instead."
|
62
|
-
Rake::Task["praxis:docs:
|
63
|
+
Rake::Task["praxis:docs:generate_old"].invoke
|
63
64
|
end
|
64
65
|
|
65
66
|
desc "Run API Documentation Browser"
|
@@ -110,7 +110,7 @@ module Praxis
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def self.file(name, payload_type=nil, filename: nil, **opts, &block)
|
113
|
-
self.part(name, payload_type
|
113
|
+
self.part(name, payload_type, filename: true, **opts, &block)
|
114
114
|
end
|
115
115
|
|
116
116
|
def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, content_type:nil)
|
data/lib/praxis/version.rb
CHANGED
data/lib/praxis.rb
CHANGED
@@ -83,6 +83,11 @@ module Praxis
|
|
83
83
|
autoload :Validation, 'praxis/exceptions/validation'
|
84
84
|
end
|
85
85
|
|
86
|
+
module Extensions
|
87
|
+
autoload :MapperSelectors, 'praxis/extensions/mapper_selectors'
|
88
|
+
autoload :Rendering, 'praxis/extensions/rendering'
|
89
|
+
autoload :FieldExpansion, 'praxis/extensions/field_expansion'
|
90
|
+
end
|
86
91
|
|
87
92
|
module Handlers
|
88
93
|
autoload :Plain, 'praxis/handlers/plain'
|
data/praxis.gemspec
CHANGED
@@ -24,9 +24,9 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency 'mustermann', '~> 0'
|
25
25
|
spec.add_dependency 'activesupport', '>= 3'
|
26
26
|
spec.add_dependency 'mime', '~> 0'
|
27
|
-
spec.add_dependency 'praxis-mapper', '
|
28
|
-
spec.add_dependency 'praxis-blueprints', '
|
29
|
-
spec.add_dependency 'attributor', '
|
27
|
+
spec.add_dependency 'praxis-mapper', '~> 4.2'
|
28
|
+
spec.add_dependency 'praxis-blueprints', '~> 3.1'
|
29
|
+
spec.add_dependency 'attributor', '~> 5.0'
|
30
30
|
spec.add_dependency 'thor', '~> 0.18'
|
31
31
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
32
32
|
spec.add_dependency 'harness', '~> 2'
|
@@ -48,4 +48,5 @@ Gem::Specification.new do |spec|
|
|
48
48
|
spec.add_development_dependency 'fuubar', '~> 2'
|
49
49
|
spec.add_development_dependency 'yard', '~> 0'
|
50
50
|
spec.add_development_dependency 'sqlite3', '~> 1'
|
51
|
+
spec.add_development_dependency 'coveralls'
|
51
52
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
describe('Configuration', function() {
|
2
|
+
var theConfigProvider, runFn;
|
3
|
+
beforeEach(function() {
|
4
|
+
angular.module('PraxisDocBrowser').config(function(ConfigurationProvider) {
|
5
|
+
theConfigProvider = ConfigurationProvider;
|
6
|
+
});
|
7
|
+
runFn = angular.module('PraxisDocBrowser')._runBlocks[0];
|
8
|
+
});
|
9
|
+
|
10
|
+
beforeEach(angular.mock.module('PraxisDocBrowser'));
|
11
|
+
|
12
|
+
beforeEach(inject(function(Configuration) {
|
13
|
+
Configuration;
|
14
|
+
}));
|
15
|
+
|
16
|
+
it('sets sensible defaults', function() {
|
17
|
+
expect(theConfigProvider).toEqual(jasmine.objectContaining({
|
18
|
+
title: 'API Browser',
|
19
|
+
versionLabel: 'API Version',
|
20
|
+
expandChildren: true
|
21
|
+
}));
|
22
|
+
});
|
23
|
+
|
24
|
+
it('assigns all things from the provider unto the root scope', inject(function($injector, $rootScope) {
|
25
|
+
$injector.invoke(runFn);
|
26
|
+
expect($rootScope).toEqual(jasmine.objectContaining({
|
27
|
+
title: 'API Browser',
|
28
|
+
versionLabel: 'API Version',
|
29
|
+
expandChildren: true
|
30
|
+
}));
|
31
|
+
}));
|
32
|
+
});
|
@@ -4,9 +4,33 @@ describe('Documentation service', function() {
|
|
4
4
|
beforeEach(angular.mock.module('PraxisDocBrowser'));
|
5
5
|
|
6
6
|
beforeEach(inject(function($rootScope, $injector) {
|
7
|
+
$httpBackend = $injector.get('$httpBackend');
|
8
|
+
$httpBackend.expectGET('api/index-new.json').respond({
|
9
|
+
versions: ['1.0', '2.0']
|
10
|
+
});
|
11
|
+
$httpBackend.expectGET('api/1.0.json').respond({
|
12
|
+
resources: {
|
13
|
+
foo: {
|
14
|
+
|
15
|
+
}
|
16
|
+
},
|
17
|
+
schemas: {
|
18
|
+
bar: {
|
19
|
+
|
20
|
+
}
|
21
|
+
}
|
22
|
+
});
|
23
|
+
$httpBackend.expectGET('api/2.0.json').respond({
|
24
|
+
resources: {
|
25
|
+
test: 'TestCTRL'
|
26
|
+
},
|
27
|
+
schemas: {
|
28
|
+
testType: 'TestType'
|
29
|
+
}
|
30
|
+
});
|
7
31
|
$scope = $rootScope.$new();
|
8
32
|
Documentation = $injector.get('Documentation');
|
9
|
-
$httpBackend
|
33
|
+
$httpBackend.flush();
|
10
34
|
}));
|
11
35
|
|
12
36
|
afterEach(function() {
|
@@ -14,37 +38,63 @@ describe('Documentation service', function() {
|
|
14
38
|
$httpBackend.verifyNoOutstandingRequest();
|
15
39
|
});
|
16
40
|
|
17
|
-
describe('#
|
18
|
-
var
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
41
|
+
describe('#versions', function() {
|
42
|
+
var versions;
|
43
|
+
beforeEach(function() {
|
44
|
+
Documentation.versions().then(function(v) {
|
45
|
+
versions = v;
|
46
|
+
});
|
47
|
+
$scope.$apply();
|
48
|
+
});
|
49
|
+
it('returns both versions', function() {
|
50
|
+
expect(versions).toEqual(['1.0', '2.0']);
|
51
|
+
});
|
52
|
+
});
|
53
|
+
|
54
|
+
describe('#items', function() {
|
55
|
+
var items;
|
56
|
+
beforeEach(function() {
|
57
|
+
Documentation.items('1.0').then(function(data) {
|
58
|
+
items = data;
|
59
|
+
});
|
60
|
+
$scope.$apply();
|
61
|
+
});
|
62
|
+
|
63
|
+
it('returns all items for 1.0', function() {
|
64
|
+
expect(items).toEqual({
|
65
|
+
resources: {
|
66
|
+
foo: { }
|
29
67
|
},
|
30
|
-
|
31
|
-
|
32
|
-
'name': 'V1::ResourceDefinitions::Users',
|
33
|
-
'media_type': 'V1-MediaTypes-User'
|
68
|
+
schemas: {
|
69
|
+
bar: {}
|
34
70
|
}
|
35
|
-
}
|
36
|
-
};
|
71
|
+
});
|
72
|
+
});
|
73
|
+
});
|
74
|
+
describe('#controller', function() {
|
75
|
+
var item;
|
76
|
+
beforeEach(function() {
|
77
|
+
Documentation.controller('2.0', 'test').then(function(data) {
|
78
|
+
item = data;
|
79
|
+
});
|
80
|
+
$scope.$apply();
|
81
|
+
});
|
82
|
+
|
83
|
+
it('returns all items for 1.0', function() {
|
84
|
+
expect(item).toEqual('TestCTRL');
|
85
|
+
});
|
86
|
+
});
|
87
|
+
describe('#type', function() {
|
88
|
+
var item;
|
37
89
|
beforeEach(function() {
|
38
|
-
|
39
|
-
|
40
|
-
result = data;
|
90
|
+
Documentation.type('2.0', 'testType').then(function(data) {
|
91
|
+
item = data;
|
41
92
|
});
|
42
|
-
$httpBackend.flush();
|
43
93
|
$scope.$apply();
|
44
94
|
});
|
45
95
|
|
46
|
-
it('returns
|
47
|
-
expect(
|
96
|
+
it('returns all items for 1.0', function() {
|
97
|
+
expect(item).toEqual('TestType');
|
48
98
|
});
|
49
99
|
});
|
50
100
|
});
|
@@ -44,30 +44,25 @@ describe('normalizeAttributes', function() {
|
|
44
44
|
test1: {
|
45
45
|
options: {
|
46
46
|
values: ['Hello'],
|
47
|
-
example: '"Hello"'
|
48
47
|
}
|
49
48
|
},
|
50
49
|
test2: {
|
51
50
|
options: {
|
52
|
-
example: '{\n "test3": "Yeah",\n "moreRecursive": {\n "test4": 3\n }\n}'
|
53
51
|
},
|
54
52
|
type: {
|
55
53
|
attributes: {
|
56
54
|
test3: {
|
57
55
|
options: {
|
58
56
|
default: 'Yeah',
|
59
|
-
example: '"Yeah"'
|
60
57
|
}
|
61
58
|
},
|
62
59
|
moreRecursive: {
|
63
60
|
options: {
|
64
|
-
example: '{\n "test4": 3\n}'
|
65
61
|
},
|
66
62
|
type: {
|
67
63
|
attributes: {
|
68
64
|
test4: {
|
69
65
|
options: {
|
70
|
-
example: '3'
|
71
66
|
}
|
72
67
|
}
|
73
68
|
}
|
@@ -2,34 +2,28 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe Praxis::Collection do
|
4
4
|
|
5
|
-
let(:
|
6
|
-
let(:example) { Volume.example('example-volume') }
|
5
|
+
let(:member_type) { Volume }
|
7
6
|
|
8
|
-
let(:snapshots) { example.snapshots }
|
9
7
|
|
10
|
-
subject(:
|
11
|
-
|
8
|
+
subject!(:collection) do
|
9
|
+
Praxis::Collection.of(member_type)
|
12
10
|
end
|
13
11
|
|
14
12
|
context '.of' do
|
15
|
-
let(:
|
13
|
+
let(:member_type) do
|
16
14
|
Class.new(Praxis::MediaType) do
|
17
15
|
identifier 'application/an-awesome-type'
|
18
16
|
end
|
19
17
|
end
|
20
18
|
|
21
|
-
subject!(:collection) do
|
22
|
-
Praxis::Collection.of(media_type)
|
23
|
-
end
|
24
|
-
|
25
19
|
its(:identifier) { should eq 'application/an-awesome-type; type=collection' }
|
26
20
|
|
27
21
|
it 'sets the collection on the media type' do
|
28
|
-
expect(
|
22
|
+
expect(member_type::Collection).to be(collection)
|
29
23
|
end
|
30
24
|
|
31
25
|
it 'returns an existing Collection type' do
|
32
|
-
expect(Praxis::Collection.of(
|
26
|
+
expect(Praxis::Collection.of(member_type)).to be(collection)
|
33
27
|
end
|
34
28
|
|
35
29
|
it 'works with explicitly-defined collections' do
|
@@ -41,15 +35,34 @@ describe Praxis::Collection do
|
|
41
35
|
subject(:type) { Volume::Collection }
|
42
36
|
its(:member_type) { should be Volume }
|
43
37
|
its(:identifier) { should eq 'application/vnd.acme.volumes' }
|
44
|
-
|
45
38
|
end
|
46
39
|
|
47
40
|
context '.member_type' do
|
48
|
-
|
41
|
+
subject(:collection) do
|
42
|
+
class Team < Praxis::Collection
|
43
|
+
member_type Person
|
44
|
+
end
|
45
|
+
Team
|
46
|
+
end
|
47
|
+
its(:member_type){ should be(Person) }
|
49
48
|
its(:member_attribute){ should be_kind_of(Attributor::Attribute) }
|
50
|
-
its('member_attribute.type'){ should be(
|
49
|
+
its('member_attribute.type'){ should be(Person) }
|
50
|
+
its(:identifier) { should eq Person.identifier + "; type=collection" }
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
|
+
context '.views' do
|
54
|
+
subject(:views) { collection.views }
|
55
|
+
its(:keys) { should match_array(member_type.views.keys)}
|
56
|
+
|
57
|
+
it 'generates CollectionViews from the member views' do
|
58
|
+
collection.views.each do |name, view|
|
59
|
+
expect(view.name).to be name
|
60
|
+
expect(view.schema).to be member_type
|
61
|
+
expect(view.contents).to eq member_type.views[name].contents
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
53
66
|
context '.load' do
|
54
67
|
let(:volume_data) do
|
55
68
|
{
|
@@ -86,19 +99,19 @@ describe Praxis::Collection do
|
|
86
99
|
end
|
87
100
|
|
88
101
|
|
89
|
-
context '#render' do
|
90
|
-
|
91
102
|
|
103
|
+
context '#render' do
|
92
104
|
context 'for members' do
|
105
|
+
let(:example) { Volume.example('example-volume') }
|
106
|
+
let(:snapshots) { example.snapshots }
|
107
|
+
|
93
108
|
let(:volume_output) { example.render(view: :default) }
|
94
109
|
|
95
|
-
subject(:output) { volume_output[:snapshots] }
|
110
|
+
subject(:output) { volume_output[:snapshots] }
|
96
111
|
|
97
112
|
it { should eq(snapshots.collect(&:render)) }
|
98
113
|
end
|
99
|
-
|
100
|
-
|
101
|
-
end
|
114
|
+
end
|
102
115
|
|
103
116
|
context '#validate' do
|
104
117
|
let(:volume_data) do
|
@@ -122,7 +135,7 @@ describe Praxis::Collection do
|
|
122
135
|
|
123
136
|
let(:volume) { Volume.load(volume_data) }
|
124
137
|
let(:snapshots) { volume.snapshots }
|
125
|
-
|
138
|
+
|
126
139
|
it 'validates' do
|
127
140
|
expect(volume.validate).to be_empty
|
128
141
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'praxis/extensions/field_selection'
|
4
|
+
|
5
|
+
describe Praxis::Extensions::FieldExpansion do
|
6
|
+
|
7
|
+
# include the ActionDefinitionExtension module directly, as that's where the
|
8
|
+
# bulk of lies, and by including this instead of the base FieldExpansion module
|
9
|
+
# we avoid the side-effect of injecting the ActionDefinitionExtension into
|
10
|
+
# the base Praxis::ActionDefinition.
|
11
|
+
let(:test_class) do
|
12
|
+
Class.new do
|
13
|
+
include Praxis::Extensions::FieldExpansion::ActionDefinitionExtension
|
14
|
+
|
15
|
+
def initialize(params)
|
16
|
+
@params = params
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :params
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:test_instance) { test_class.new(test_params) }
|
24
|
+
|
25
|
+
let(:request_params) do
|
26
|
+
double('params',
|
27
|
+
fields: Praxis::Extensions::FieldSelection::FieldSelector.for(Person).load(fields),
|
28
|
+
view: view
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:request) { double('Praxis::Request', params: request_params) }
|
33
|
+
let(:media_type) { Person }
|
34
|
+
|
35
|
+
let(:fields) { nil }
|
36
|
+
let(:view) { nil }
|
37
|
+
|
38
|
+
let(:test_attributes) { }
|
39
|
+
let(:test_params) { double('test_params', attributes: test_attributes) }
|
40
|
+
|
41
|
+
subject(:expansion) { test_instance.expanded_fields(request, media_type)}
|
42
|
+
|
43
|
+
context '#expanded_fields' do
|
44
|
+
|
45
|
+
context 'with fields and view params defined' do
|
46
|
+
let(:test_attributes) { {view: true, fields: true} }
|
47
|
+
|
48
|
+
context 'and no fields provided' do
|
49
|
+
it 'returns the fields for the default view' do
|
50
|
+
expect(expansion).to eq({id: true, name: true, links: [true]})
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'and a view' do
|
54
|
+
let(:view) { :link }
|
55
|
+
it 'expands the fields on the view' do
|
56
|
+
expect(expansion).to eq({id: true, name: true, href: true})
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with a set of fields provided' do
|
62
|
+
let(:fields) { 'id,name,owner{name}' }
|
63
|
+
it 'returns the subset of fields for the default view' do
|
64
|
+
expected = {id: true, name: true }
|
65
|
+
expect(expansion).to eq expected
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'and a view' do
|
69
|
+
let(:view) { :link }
|
70
|
+
let(:fields) { 'id,href' }
|
71
|
+
|
72
|
+
it 'returns the subset of fields that exist for the view' do
|
73
|
+
expected = {id: true, href: true }
|
74
|
+
expect(expansion).to eq expected
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'with only a view param defined' do
|
81
|
+
let(:test_attributes) { {view: true} }
|
82
|
+
|
83
|
+
it 'returns the fields for the default view' do
|
84
|
+
expect(expansion).to eq({id: true, name: true, links: [true]})
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'and a view' do
|
88
|
+
let(:view) { :link }
|
89
|
+
it 'expands the fields on the view' do
|
90
|
+
expect(expansion).to eq({id: true, name: true, href: true})
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|