praxis 0.18.1 → 0.19.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.
- 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
|