praxis 0.9 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|