praxis 0.9 → 0.10.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/.travis.yml +1 -0
- data/CHANGELOG.md +51 -0
- data/CONTRIBUTING.md +3 -2
- data/Gemfile +1 -1
- data/README.md +5 -3
- data/lib/api_browser/app/css/main.css +4 -0
- data/lib/api_browser/app/js/directives/attribute_description.js +17 -14
- data/lib/api_browser/app/js/directives/type_label.js +7 -7
- data/lib/api_browser/app/sass/modules/_body.scss +8 -1
- data/lib/api_browser/app/views/directives/attribute_description/_example.html +9 -4
- data/lib/api_browser/app/views/directives/attribute_table_row/_links.html +4 -3
- data/lib/api_browser/app/views/resource/_actions.html +11 -5
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +6 -6
- data/lib/praxis/action_definition.rb +19 -4
- data/lib/praxis/api_definition.rb +3 -3
- data/lib/praxis/application.rb +1 -0
- data/lib/praxis/bootloader_stages/routing.rb +3 -3
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +1 -0
- data/lib/praxis/callbacks.rb +35 -0
- data/lib/praxis/controller.rb +18 -35
- data/lib/praxis/file_group.rb +12 -7
- data/lib/praxis/links.rb +4 -0
- data/lib/praxis/media_type_collection.rb +21 -15
- data/lib/praxis/request.rb +2 -7
- data/lib/praxis/request_stages/action.rb +10 -7
- data/lib/praxis/request_stages/request_stage.rb +45 -10
- data/lib/praxis/request_stages/validate.rb +7 -3
- data/lib/praxis/responses/http.rb +0 -24
- data/lib/praxis/responses/internal_server_error.rb +40 -0
- data/lib/praxis/responses/validation_error.rb +1 -0
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +5 -24
- data/praxis.gemspec +3 -3
- data/spec/functional_spec.rb +62 -17
- data/spec/praxis/action_definition_spec.rb +14 -6
- data/spec/praxis/callbacks_spec.rb +107 -0
- data/spec/praxis/controller_spec.rb +0 -38
- data/spec/praxis/file_group_spec.rb +5 -0
- data/spec/praxis/media_type_collection_spec.rb +20 -7
- data/spec/praxis/request_spec.rb +15 -1
- data/spec/praxis/request_stage_spec.rb +93 -0
- data/spec/praxis/request_stages_action_spec.rb +61 -0
- data/spec/praxis/request_stages_validate_spec.rb +1 -1
- data/spec/praxis/responses/internal_server_error_spec.rb +51 -0
- data/spec/praxis/responses/validation_error_spec.rb +66 -0
- data/spec/praxis/router_spec.rb +5 -1
- data/spec/spec_app/app/concerns/authenticated.rb +15 -0
- data/spec/spec_app/app/concerns/basic_api.rb +12 -0
- data/spec/spec_app/app/concerns/log_wrapper.rb +14 -0
- data/spec/spec_app/app/controllers/base_class.rb +14 -0
- data/spec/spec_app/app/controllers/instances.rb +31 -5
- data/spec/spec_app/app/controllers/volumes.rb +17 -0
- data/spec/spec_app/config/environment.rb +1 -0
- data/spec/spec_app/design/api.rb +3 -3
- data/spec/spec_app/design/media_types/volume.rb +1 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +1 -0
- data/spec/spec_app/design/resources/instances.rb +10 -3
- data/spec/spec_app/design/resources/volumes.rb +27 -0
- data/spec/support/spec_resource_definitions.rb +32 -0
- data/tasks/praxis_app_generator.thor +2 -2
- metadata +23 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ef88025b412d1a511b45aaeebc4da3dc77e26ba
|
4
|
+
data.tar.gz: 12055f1821e6dbb2bf2db0db9243cc41c49ca8ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3976e3f0b241efbad32a3b1b1ff65b0aaad3c9b6e6c7d38e00f611441f2f9c4ee2a75ea4c389cbe7a055795a6e8da0713d191f1d142310beaf7c4ca9a88265d5
|
7
|
+
data.tar.gz: 8f609d4f6704949f59f6ab8fc35a9319909cec4ed90275abf03f2103fa1ddd11fb851a21a01fbf98ee00a4c6ad30b1d52542e33804ff94cb6957a532327b4468
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# praxis changelog
|
2
|
+
|
3
|
+
## next
|
4
|
+
|
5
|
+
* next thing here
|
6
|
+
|
7
|
+
## 0.10.0
|
8
|
+
|
9
|
+
* Avoid loading responses (and templates) lazily as they need to be registered in time
|
10
|
+
* Fix: app generator's handling of 404. [@magneland](https://github.com/magneland) [Issue #10](https://github.com/rightscale/praxis/issues/10)
|
11
|
+
* Fix: Getting started doc. [@WilliamSnyders](https://github.com/WilliamSnyders) [Issue #19](https://github.com/rightscale/praxis/issues/19)
|
12
|
+
* Controller filters can now shortcut the request lifecycle flow by returning a `Response`:
|
13
|
+
* If a before filter returns it, both the action and the after filters will be skipped (as well as any remaining filters in the before list)
|
14
|
+
* If an after filter returns it, any remaining after filters in the block will be skipped.
|
15
|
+
* There is no way for the action result to skip the :after filters.
|
16
|
+
* Refactored Controller module to properly used ActiveSupprt concerns. [@jasonayre](https://github.com/jasonayre) [Issue #26](https://github.com/rightscale/praxis/issues/26)
|
17
|
+
* Separated the controller module into a Controller concern and a separable Callbacks concern
|
18
|
+
* Controller filters (i.e. callbacks) can shortcut request lifecycle by returning a Response object:
|
19
|
+
* If a before filter returns it, both the action and the after filters will be skipped (as well as any remaining before filters)
|
20
|
+
* If an after filter returns it, any remaining after filters in the block will be skipped.
|
21
|
+
* There is no way for the action result to skip the :after filters.
|
22
|
+
* Fixes [Issue #21](https://github.com/rightscale/praxis/issues/21)
|
23
|
+
* Introduced `around` filters using blocks:
|
24
|
+
* Around filters can be set wrapping any of the request stages (load, validate, action...) and might apply to only certain actions (i.e. exactly the same as the before/after filters)
|
25
|
+
* Therefore they supports the same attributes as `before` and `after` filters. The only difference is that an around filter block will get an extra parameter with the block to call to continue the chain.
|
26
|
+
* See the [Instances](https://github.com/rightscale/praxis/blob/master/spec/spec_app/app/controllers/instances.rb) controller for examples.
|
27
|
+
* Fix: Change :created response template to take an optiona ‘location’ parameter (instead of a media_type one, since it doesn’t make sense for a 201 type response) [Issue #26](https://github.com/rightscale/praxis/issues/23)
|
28
|
+
* Make the system be more robust in error reporting when controllers do not return a String or a Response
|
29
|
+
* Fix: ValidationError not setting a Content-Type header. [Issue #39](https://github.com/rightscale/praxis/issues/19)
|
30
|
+
* Relaxed ActiveSupport version dependency (from 4 to >=3 )
|
31
|
+
* Fix: InternalServerError not setting a Content-Type header. [Issue #42](https://github.com/rightscale/praxis/issues/42)
|
32
|
+
* A few document browser improvements:
|
33
|
+
* Avoid showing certain internal type options (i.e. reference).
|
34
|
+
* Fixed type label cide to detect collections better, and differentiate between Attributor ones and MediaType ones.
|
35
|
+
* Tweaked _example.html template to be much more collapsed by default, as it is not great, but makes it easier to review.
|
36
|
+
* Enhanced _links.html template to use the rs-type-label, and rs-attribute-description directives.
|
37
|
+
* Small design improvements on the home page for showing routes and verbs more prominently.
|
38
|
+
* Mediatype documentation improvements:
|
39
|
+
* Make `Links` always list its attributes when describe (never shallow)
|
40
|
+
* refactored MediaTypeCollection to store a member_attribute (instead of a member_type), and report it in describe much like attributor collections do.
|
41
|
+
* `MediaTypeCollection`. See [volume_snapshot](spec/spec_app/design/media_types/volume_snapshot.rb) in the specs for an example.
|
42
|
+
* Added `member_view` DSL to define a view that renders the collection's members with the given view.
|
43
|
+
* Change: Now requires all views to be explicitly defined (and will not automatically use the underlying member view if it exists). To define a view for member element (wrapping it in a collection) one can use the new member_view.
|
44
|
+
* Application.setup now returns the application instance so it can be chained to other application methods.
|
45
|
+
* This allows a more compact way to rackup the application: `run Praxis::Application.instance.setup`.
|
46
|
+
* Request headers are now backed by `Attributor::Hash` rather than (Structs)
|
47
|
+
** Header keys are case sensitive now (although Praxis gracefully allows loading the uppercased RACK names )
|
48
|
+
* Added tests for using `Attributor::Hash` attributes that can accept "extra" keys:
|
49
|
+
** See the `bulk_create` action for an example applied to `Praxis::Multipart` (which derives from `Attributor::Hash`) payload [here](https://github.com/rightscale/praxis/blob/master/spec/spec_app/design/resources/instances.rb).
|
50
|
+
|
51
|
+
## 0.9 Initial release
|
data/CONTRIBUTING.md
CHANGED
@@ -103,8 +103,9 @@ logical units of work using `git rebase -i` and `git push -f`. After every
|
|
103
103
|
commit the test suite should be passing. Include documentation changes in the
|
104
104
|
same commit so that a revert would remove all traces of the feature or fix.
|
105
105
|
|
106
|
-
Commits that fix or close an issue should include a reference like
|
107
|
-
#XXXX` or `Fixes #XXXX`, which will automatically close the issue when
|
106
|
+
Commits that fix or close an issue should include a reference like
|
107
|
+
`Closes #XXXX` or `Fixes #XXXX`, which will automatically close the issue when
|
108
|
+
merged.
|
108
109
|
|
109
110
|
Please do not add yourself to the `AUTHORS` file unless you are on the Praxis
|
110
111
|
github team.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -48,12 +48,14 @@ job keeping the docs and the code in sync everytime they make code changes.
|
|
48
48
|
|
49
49
|
## Mailing List
|
50
50
|
Join our Google Groups for discussion, support and announcements.
|
51
|
-
* http://groups.google.com/d/forum/praxis-support (support for people using
|
51
|
+
* [praxis-support](http://groups.google.com/d/forum/praxis-support) (support for people using
|
52
52
|
Praxis)
|
53
|
-
* http://groups.google.com/d/forum/praxis-announce (announcements)
|
54
|
-
* http://groups.google.com/d/forum/praxis-development (discussion about the
|
53
|
+
* [praxis-announce](http://groups.google.com/d/forum/praxis-announce) (announcements)
|
54
|
+
* [praxis-development](http://groups.google.com/d/forum/praxis-development) (discussion about the
|
55
55
|
development of Praxis itself)
|
56
56
|
|
57
|
+
And follow us on twitter: [@praxisapi](http://twitter.com/praxisapi)
|
58
|
+
|
57
59
|
## Contributions
|
58
60
|
Contributions to make Praxis better are welcome. Please refer to
|
59
61
|
[CONTRIBUTING](https://github.com/rightscale/praxis/blob/master/CONTRIBUTING.md)
|
@@ -4376,6 +4376,10 @@ body {
|
|
4376
4376
|
body a {
|
4377
4377
|
cursor: pointer; }
|
4378
4378
|
|
4379
|
+
.content dl {
|
4380
|
+
margin-top: 0;
|
4381
|
+
margin-bottom: 0; }
|
4382
|
+
|
4379
4383
|
.header .navbar-default {
|
4380
4384
|
background-image: -ms-linear-gradient(top, #fff 0%, #f3f3f3 100%);
|
4381
4385
|
background-image: -moz-linear-gradient(top, #fff 0%, #f3f3f3 100%);
|
@@ -10,7 +10,8 @@
|
|
10
10
|
|
11
11
|
_.forEach(scope.attribute.options, function(option, name) {
|
12
12
|
var templatePath = 'views/directives/attribute_description/_default.html';
|
13
|
-
|
13
|
+
var skip_keys = ['reference','dsl_compiler','dsl_compiler_options'];
|
14
|
+
|
14
15
|
switch (name) {
|
15
16
|
case 'example':
|
16
17
|
// expects string
|
@@ -24,19 +25,21 @@
|
|
24
25
|
break;
|
25
26
|
}
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
28
|
+
if( ! _.contains( skip_keys, name ) ) {
|
29
|
+
$http.get(templatePath, { cache: $templateCache }).success(function(template) {
|
30
|
+
var row = $(template);
|
31
|
+
var rowScope = scope.$new(true);
|
32
|
+
|
33
|
+
rowScope.row = {
|
34
|
+
name: name,
|
35
|
+
value: option
|
36
|
+
};
|
37
|
+
|
38
|
+
$compile(row)(rowScope);
|
39
|
+
list.append(row);
|
40
|
+
});
|
41
|
+
}
|
42
|
+
|
40
43
|
})
|
41
44
|
}
|
42
45
|
};
|
@@ -16,8 +16,8 @@
|
|
16
16
|
var templates = {
|
17
17
|
primitive: '<span>{{type.name}}</span>',
|
18
18
|
type: '<a ui-sref="root.type({version: apiVersion, type: type.name})">{{type.name | resourceName}}</a>',
|
19
|
-
|
20
|
-
|
19
|
+
primitiveCollection: '<span>Collection [ {{type.member_attribute.type.name}} ]</a>',
|
20
|
+
typeCollection: '<span>Collection [ <a ui-sref="root.type({version: apiVersion, type: type.member_attribute.type.name})">{{type.member_attribute.type.name | resourceName}}</a> ]</span>',
|
21
21
|
link: '<span>Link [ <a ui-sref="root.type({version: apiVersion, type: type.link_to})">{{type.link_to | resourceName}}</a> ]</span>'
|
22
22
|
};
|
23
23
|
|
@@ -29,15 +29,15 @@
|
|
29
29
|
link: function(scope, element) {
|
30
30
|
|
31
31
|
var template = templates.type;
|
32
|
-
|
33
32
|
if (_.contains(primitives, scope.type.name)) {
|
34
33
|
template = templates.primitive;
|
35
34
|
}
|
36
|
-
else if (scope.type.
|
37
|
-
if (_.contains(primitives, scope.type.
|
38
|
-
template = templates.typeCollection;
|
39
|
-
else
|
35
|
+
else if ( scope.type.member_attribute !== undefined ) {
|
36
|
+
if ( _.contains(primitives, scope.type.member_attribute.type.name)){
|
40
37
|
template = templates.primitiveCollection;
|
38
|
+
}else{
|
39
|
+
template = templates.typeCollection;
|
40
|
+
}
|
41
41
|
}
|
42
42
|
else if (scope.type.link_to) {
|
43
43
|
template = templates.link;
|
@@ -1,8 +1,13 @@
|
|
1
|
-
<dt ng-if="row.value.length <
|
2
|
-
<dt ng-if="row.value.length >=
|
3
|
-
|
1
|
+
<dt ng-if="row.value.length < 50">example</dt>
|
2
|
+
<dt ng-if="row.value.length >= 50">
|
3
|
+
example (<a ng-init="row.isCollapsed = true" ng-click="row.isCollapsed = !row.isCollapsed">{{row.isCollapsed? 'show' : 'hide'}}</a>)
|
4
|
+
</dt>
|
4
5
|
</dt>
|
5
|
-
|
6
|
+
|
7
|
+
|
8
|
+
<dd ng-if="row.value.length <= 50">
|
9
|
+
<code ng-class="{hide: false}">{{row.value}}</code>
|
10
|
+
</dd>
|
6
11
|
<dd ng-if="row.value.length > 50">
|
7
12
|
<pre ng-class="{hide: row.isCollapsed}">{{row.value}}</pre>
|
8
13
|
</dd>
|
@@ -1,10 +1,11 @@
|
|
1
1
|
<ng-include src="'views/directives/attribute_table_row/_default.html'" no-container></ng-include>
|
2
|
-
<div class="row" ng-repeat="(
|
3
|
-
<div class="col-sm-3" ng-bind-html="name + '.' +
|
2
|
+
<div class="row" ng-repeat="(attribute_name,attribute) in attribute.type.attributes">
|
3
|
+
<div class="col-sm-3" ng-bind-html="name + '.' + attribute_name | attributeName">
|
4
4
|
</div>
|
5
5
|
<div class="col-sm-2">
|
6
|
+
<rs-type-label type="attribute.type"></rs-type-label>
|
6
7
|
</div>
|
7
8
|
<div class="col-sm-7">
|
8
|
-
|
9
|
+
<rs-attribute-description attribute="attribute"></rs-attribute-description>
|
9
10
|
</div>
|
10
11
|
</div>
|
@@ -1,21 +1,27 @@
|
|
1
1
|
<div class="attribute-table">
|
2
2
|
<div class="row head">
|
3
|
-
<div class="col-sm-
|
3
|
+
<div class="col-sm-2">
|
4
4
|
Name
|
5
5
|
</div>
|
6
|
-
<div class="col-sm-
|
6
|
+
<div class="col-sm-4">
|
7
|
+
Route
|
8
|
+
</div>
|
9
|
+
<div class="col-sm-6">
|
7
10
|
Description
|
8
11
|
</div>
|
9
12
|
</div>
|
10
13
|
<div class="content">
|
11
14
|
<div class="row" ng-repeat="action in controller.actions">
|
12
|
-
<div class="col-sm-
|
15
|
+
<div class="col-sm-2">
|
13
16
|
<a ui-sref="root.action({action: action.name, controller: controllerName, version: apiVersion})">{{ action.name }}</a>
|
14
17
|
</div>
|
15
|
-
<div class="col-sm-
|
18
|
+
<div class="col-sm-4">
|
16
19
|
<div ng-repeat="url in action.urls">
|
17
|
-
|
20
|
+
<div class="label label-default">{{ url.verb }}</div>
|
21
|
+
<strong>{{ url.path }}</strong>
|
18
22
|
</div>
|
23
|
+
</div>
|
24
|
+
<div class="col-sm-6" style="font-size: 110%;">
|
19
25
|
{{ action.description }}
|
20
26
|
</div>
|
21
27
|
</div>
|
@@ -2,7 +2,6 @@ module Praxis
|
|
2
2
|
class ActionDefinition
|
3
3
|
class HeadersDSLCompiler < Attributor::DSLCompiler
|
4
4
|
|
5
|
-
alias_method :orig_attribute, :attribute
|
6
5
|
# it allows to define expectations on incoming headers. For example:
|
7
6
|
# header :X_SpecialCookie => implies the header is required
|
8
7
|
# header :X_Something, /matching_this/ => implies that if the name header exists, it should match the regexp
|
@@ -22,13 +21,14 @@ module Praxis
|
|
22
21
|
# Defining the existence without any other options can only mean that it is required (otherwise it is a useless definition)
|
23
22
|
options[:required] = true if options.empty?
|
24
23
|
end
|
25
|
-
orig_attribute name.upcase , String, options
|
24
|
+
#orig_attribute name.upcase , String, options
|
25
|
+
key name , String, options
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
)
|
28
|
+
# Override the attribute to really call "key" in the hash (for temporary backwards compat)
|
29
|
+
def attribute(name, attr_type=nil, **opts, &block)
|
30
|
+
warn "[DEPRECATION] `attribute` is deprecated when defining headers. Please use `key` instead."
|
31
|
+
key(name, attr_type, **opts, &block)
|
32
32
|
end
|
33
33
|
|
34
34
|
end
|
@@ -16,7 +16,6 @@ module Praxis
|
|
16
16
|
attr_reader :named_routes
|
17
17
|
attr_reader :responses
|
18
18
|
|
19
|
-
|
20
19
|
def initialize(name, resource_definition, **opts, &block)
|
21
20
|
@name = name
|
22
21
|
@resource_definition = resource_definition
|
@@ -105,15 +104,31 @@ module Praxis
|
|
105
104
|
end
|
106
105
|
end
|
107
106
|
|
108
|
-
def headers(**opts, &block)
|
107
|
+
def headers(type=Attributor::Hash.of(key:String), **opts, &block)
|
109
108
|
return @headers unless block
|
110
|
-
|
111
109
|
if @headers
|
112
110
|
update_attribute(@headers, opts, block)
|
113
111
|
else
|
114
|
-
@headers = create_attribute(
|
112
|
+
@headers = create_attribute(type,
|
113
|
+
dsl_compiler: HeadersDSLCompiler, case_insensitive_load: true,
|
114
|
+
**opts, &block)
|
115
|
+
end
|
116
|
+
@precomputed_header_keys_for_rack = nil #clear memoized data
|
117
|
+
end
|
118
|
+
|
119
|
+
# Good optimization to avoid creating lots of strings and comparisons
|
120
|
+
# on a per-request basis.
|
121
|
+
# However, this is hacky, as it is rack-specific, and does not really belong here
|
122
|
+
def precomputed_header_keys_for_rack
|
123
|
+
@precomputed_header_keys_for_rack ||= begin
|
124
|
+
@headers.attributes.keys.each_with_object(Hash.new) do |key,hash|
|
125
|
+
name = key.to_s
|
126
|
+
name = "HTTP_#{name.upcase}" unless ( name == "CONTENT_TYPE" || name == "CONTENT_LENGTH" )
|
127
|
+
hash[name] = key
|
128
|
+
end
|
115
129
|
end
|
116
130
|
end
|
131
|
+
|
117
132
|
|
118
133
|
def routing(&block)
|
119
134
|
routing_config = Skeletor::RestfulRoutingConfig.new(name, resource_definition, &block)
|
@@ -30,7 +30,7 @@ module Praxis
|
|
30
30
|
|
31
31
|
def response(name)
|
32
32
|
return @responses.fetch(name) do
|
33
|
-
raise ArgumentError, "no response defined with name #{name.inspect}"
|
33
|
+
raise ArgumentError, "no response template defined with name #{name.inspect}. Are you forgetting to register it with ApiDefinition?"
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -48,8 +48,8 @@ module Praxis
|
|
48
48
|
status 200
|
49
49
|
end
|
50
50
|
|
51
|
-
api.response_template :created do |
|
52
|
-
|
51
|
+
api.response_template :created do |location: nil|
|
52
|
+
location location
|
53
53
|
status 201
|
54
54
|
end
|
55
55
|
end
|
data/lib/praxis/application.rb
CHANGED
@@ -7,7 +7,7 @@ module Praxis
|
|
7
7
|
|
8
8
|
def execute
|
9
9
|
application.controllers.each do |controller|
|
10
|
-
controller.actions.each do |action_name, action|
|
10
|
+
controller.definition.actions.each do |action_name, action|
|
11
11
|
action.routes.each do |route|
|
12
12
|
target = target_factory(controller, action_name)
|
13
13
|
application.router.add_route target, route
|
@@ -18,11 +18,11 @@ module Praxis
|
|
18
18
|
|
19
19
|
|
20
20
|
def target_factory(controller, action_name)
|
21
|
-
action = controller.
|
21
|
+
action = controller.definition.actions.fetch(action_name)
|
22
22
|
|
23
23
|
Proc.new do |request|
|
24
24
|
request.action = action
|
25
|
-
dispatcher = Dispatcher.current(
|
25
|
+
dispatcher = Dispatcher.current( application: application)
|
26
26
|
|
27
27
|
dispatcher.dispatch(controller, action, request)
|
28
28
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Praxis
|
4
|
+
module Callbacks
|
5
|
+
extend ::ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :before_callbacks, :after_callbacks, :around_callbacks
|
9
|
+
self.before_callbacks = Hash.new
|
10
|
+
self.after_callbacks = Hash.new
|
11
|
+
self.around_callbacks = Hash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def before(*stage_path, **conditions, &block)
|
17
|
+
stage_path = [:action] if stage_path.empty?
|
18
|
+
before_callbacks[stage_path] ||= Array.new
|
19
|
+
before_callbacks[stage_path] << [conditions, block]
|
20
|
+
end
|
21
|
+
|
22
|
+
def after(*stage_path, **conditions, &block)
|
23
|
+
stage_path = [:action] if stage_path.empty?
|
24
|
+
after_callbacks[stage_path] ||= Array.new
|
25
|
+
after_callbacks[stage_path] << [conditions, block]
|
26
|
+
end
|
27
|
+
|
28
|
+
def around(*stage_path, **conditions, &block)
|
29
|
+
stage_path = [:action] if stage_path.empty?
|
30
|
+
around_callbacks[stage_path] ||= Array.new
|
31
|
+
around_callbacks[stage_path] << [conditions, block]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/praxis/controller.rb
CHANGED
@@ -1,59 +1,42 @@
|
|
1
1
|
require 'active_support/concern'
|
2
|
-
require 'active_support/
|
2
|
+
require 'active_support/all'
|
3
3
|
|
4
4
|
module Praxis
|
5
|
-
|
6
5
|
module Controller
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
6
|
+
extend ::ActiveSupport::Concern
|
7
|
+
# A Controller always requires the callbacks
|
8
|
+
include Praxis::Callbacks
|
9
|
+
|
9
10
|
included do
|
10
11
|
attr_reader :request
|
11
12
|
attr_accessor :response
|
12
|
-
Application.instance.controllers << self
|
13
|
-
self.instance_eval do
|
14
|
-
@before_callbacks = Hash.new
|
15
|
-
@after_callbacks = Hash.new
|
16
|
-
end
|
17
|
-
|
18
13
|
end
|
19
|
-
|
20
14
|
module ClassMethods
|
21
|
-
attr_reader :before_callbacks, :after_callbacks
|
22
|
-
|
23
15
|
def implements(definition)
|
24
16
|
define_singleton_method(:definition) do
|
25
17
|
definition
|
26
18
|
end
|
27
|
-
definition.controller = self
|
28
|
-
end
|
29
19
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def action(name)
|
35
|
-
actions.fetch(name)
|
36
|
-
end
|
37
|
-
|
38
|
-
def before(*stage_path, **conditions, &block)
|
39
|
-
stage_path = [:action] if stage_path.empty?
|
40
|
-
@before_callbacks[stage_path] ||= Array.new
|
41
|
-
@before_callbacks[stage_path] << [conditions, block]
|
42
|
-
end
|
43
|
-
|
44
|
-
def after(*stage_path, **conditions, &block)
|
45
|
-
stage_path = [:action] if stage_path.empty?
|
46
|
-
@after_callbacks[stage_path] ||= Array.new
|
47
|
-
@after_callbacks[stage_path] << [conditions, block]
|
20
|
+
definition.controller = self
|
21
|
+
Application.instance.controllers << self
|
48
22
|
end
|
49
|
-
|
50
23
|
end
|
51
24
|
|
52
|
-
|
53
25
|
def initialize(request, response=Responses::Ok.new)
|
54
26
|
@request = request
|
55
27
|
@response = response
|
56
28
|
end
|
57
29
|
|
30
|
+
# def request
|
31
|
+
# @request
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def response
|
35
|
+
# @response
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# def response=(value)
|
39
|
+
# @response = value
|
40
|
+
# end
|
58
41
|
end
|
59
42
|
end
|
data/lib/praxis/file_group.rb
CHANGED
@@ -5,8 +5,14 @@ module Praxis
|
|
5
5
|
attr_reader :groups, :base
|
6
6
|
|
7
7
|
def initialize(base, &block)
|
8
|
+
if base.nil?
|
9
|
+
raise ArgumentError, "base must not be nil." \
|
10
|
+
"Are you missing a call Praxis::Application.instance.setup?"
|
11
|
+
end
|
12
|
+
|
13
|
+
|
8
14
|
@groups = Hash.new
|
9
|
-
@base = base
|
15
|
+
@base = Pathname.new(base)
|
10
16
|
|
11
17
|
if block_given?
|
12
18
|
self.instance_eval(&block)
|
@@ -18,16 +24,15 @@ module Praxis
|
|
18
24
|
end
|
19
25
|
|
20
26
|
def map(name, pattern, &block)
|
27
|
+
return unless base.exist?
|
28
|
+
|
21
29
|
if block_given?
|
22
30
|
@groups[name] = FileGroup.new(base + pattern, &block)
|
23
31
|
else
|
24
32
|
@groups[name] ||= []
|
25
|
-
|
26
|
-
files
|
27
|
-
|
28
|
-
file.file? && path.fnmatch?(pattern, File::FNM_PATHNAME)
|
29
|
-
end
|
30
|
-
files.sort_by { |file| [file.to_s.split('/').size, file.to_s] }.each { |file| @groups[name] << file }
|
33
|
+
files = Pathname.glob(base+pattern).select { |file| file.file? }
|
34
|
+
files.sort_by! { |file| [file.to_s.split('/').size, file.to_s] }
|
35
|
+
files.each { |file| @groups[name] << file }
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
data/lib/praxis/links.rb
CHANGED
@@ -23,6 +23,10 @@ module Praxis
|
|
23
23
|
class MediaTypeCollection < MediaType
|
24
24
|
include Enumerable
|
25
25
|
|
26
|
+
class << self
|
27
|
+
attr_accessor :member_attribute
|
28
|
+
end
|
29
|
+
|
26
30
|
def self._finalize!
|
27
31
|
super
|
28
32
|
|
@@ -35,10 +39,11 @@ module Praxis
|
|
35
39
|
end
|
36
40
|
|
37
41
|
def self.member_type(type=nil)
|
38
|
-
return @
|
42
|
+
return ( @member_attribute ? @member_attribute.type : nil) unless type
|
39
43
|
raise ArgumentError, "invalid type: #{type.name}" unless type < MediaType
|
40
44
|
|
41
|
-
|
45
|
+
member_options = {}
|
46
|
+
@member_attribute = Attributor::Attribute.new type, member_options
|
42
47
|
end
|
43
48
|
|
44
49
|
def self.example(context=nil, options: {})
|
@@ -59,10 +64,10 @@ module Praxis
|
|
59
64
|
|
60
65
|
size.times do |i|
|
61
66
|
subcontext = context + ["at(#{i})"]
|
62
|
-
members << @
|
67
|
+
members << @member_attribute.example(subcontext)
|
63
68
|
end
|
64
69
|
|
65
|
-
|
70
|
+
|
66
71
|
result.object._members = members
|
67
72
|
result
|
68
73
|
end
|
@@ -80,7 +85,7 @@ module Praxis
|
|
80
85
|
self.new(self.attribute.load(value,context, **options))
|
81
86
|
when Array
|
82
87
|
object = self.attribute.load({})
|
83
|
-
object._members = value.collect { |subvalue| @
|
88
|
+
object._members = value.collect { |subvalue| @member_attribute.load(subvalue) }
|
84
89
|
self.new(object)
|
85
90
|
else
|
86
91
|
# Just wrap whatever value
|
@@ -88,18 +93,19 @@ module Praxis
|
|
88
93
|
end
|
89
94
|
end
|
90
95
|
|
96
|
+
def self.describe(shallow = false)
|
97
|
+
hash = super
|
98
|
+
hash[:member_attribute] = member_attribute.describe(true)
|
99
|
+
hash
|
100
|
+
end
|
91
101
|
|
92
|
-
def
|
93
|
-
if
|
94
|
-
|
95
|
-
|
96
|
-
else
|
97
|
-
# render each member with the view
|
98
|
-
@object.collect.with_index do |member, i|
|
99
|
-
subcontext = context + ["at(#{i})"]
|
100
|
-
member.render(view_name, context: subcontext)
|
101
|
-
end
|
102
|
+
def self.member_view(name, using: nil)
|
103
|
+
if using
|
104
|
+
member_view = self.member_type.view(using)
|
105
|
+
return self.views[name] = CollectionView.new(name, self, member_view)
|
102
106
|
end
|
107
|
+
|
108
|
+
self.views[name]
|
103
109
|
end
|
104
110
|
|
105
111
|
|