moonrope 1.4.1 → 2.0.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/Gemfile +9 -0
- data/Gemfile.lock +47 -0
- data/MIT-LICENCE +20 -0
- data/README.md +24 -0
- data/bin/moonrope +28 -0
- data/docs/authentication.md +114 -0
- data/docs/controllers.md +106 -0
- data/docs/exceptions.md +27 -0
- data/docs/introduction.md +29 -0
- data/docs/structures.md +214 -0
- data/example/authentication.rb +50 -0
- data/example/controllers/meta_controller.rb +14 -0
- data/example/controllers/users_controller.rb +92 -0
- data/example/structures/pet_structure.rb +12 -0
- data/example/structures/user_structure.rb +35 -0
- data/html/assets/lock.svg +3 -0
- data/html/assets/reset.css +101 -0
- data/html/assets/style.css +348 -0
- data/html/assets/tool.svg +4 -0
- data/html/assets/try.js +151 -0
- data/html/authenticators/default.html +191 -0
- data/html/controllers/meta/version.html +144 -0
- data/html/controllers/meta.html +73 -0
- data/html/controllers/users/create.html +341 -0
- data/html/controllers/users/list.html +348 -0
- data/html/controllers/users/show.html +261 -0
- data/html/controllers/users/update.html +387 -0
- data/html/controllers/users.html +93 -0
- data/html/index.html +166 -0
- data/html/moonrope.txt +0 -0
- data/html/structures/pet.html +176 -0
- data/html/structures/user.html +338 -0
- data/lib/moonrope/action.rb +165 -37
- data/lib/moonrope/authenticator.rb +39 -0
- data/lib/moonrope/base.rb +24 -6
- data/lib/moonrope/controller.rb +4 -2
- data/lib/moonrope/doc_context.rb +94 -0
- data/lib/moonrope/doc_server.rb +123 -0
- data/lib/moonrope/dsl/action_dsl.rb +159 -9
- data/lib/moonrope/dsl/authenticator_dsl.rb +31 -0
- data/lib/moonrope/dsl/base_dsl.rb +21 -18
- data/lib/moonrope/dsl/controller_dsl.rb +60 -9
- data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
- data/lib/moonrope/dsl/structure_dsl.rb +27 -2
- data/lib/moonrope/errors.rb +3 -0
- data/lib/moonrope/eval_environment.rb +82 -3
- data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
- data/lib/moonrope/eval_helpers.rb +28 -5
- data/lib/moonrope/guard.rb +35 -0
- data/lib/moonrope/html_generator.rb +65 -0
- data/lib/moonrope/param_set.rb +11 -1
- data/lib/moonrope/rack_middleware.rb +1 -1
- data/lib/moonrope/railtie.rb +31 -14
- data/lib/moonrope/request.rb +25 -14
- data/lib/moonrope/structure.rb +74 -11
- data/lib/moonrope/structure_attribute.rb +15 -0
- data/lib/moonrope/version.rb +1 -1
- data/lib/moonrope.rb +5 -4
- data/moonrope.gemspec +21 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/specs/action_spec.rb +455 -0
- data/spec/specs/base_spec.rb +29 -0
- data/spec/specs/controller_spec.rb +31 -0
- data/spec/specs/param_set_spec.rb +31 -0
- data/templates/basic/_action_form.erb +77 -0
- data/templates/basic/_errors_table.erb +32 -0
- data/templates/basic/_structure_attributes_list.erb +55 -0
- data/templates/basic/action.erb +168 -0
- data/templates/basic/assets/lock.svg +3 -0
- data/templates/basic/assets/reset.css +101 -0
- data/templates/basic/assets/style.css +348 -0
- data/templates/basic/assets/tool.svg +4 -0
- data/templates/basic/assets/try.js +151 -0
- data/templates/basic/authenticator.erb +51 -0
- data/templates/basic/controller.erb +20 -0
- data/templates/basic/index.erb +114 -0
- data/templates/basic/layout.erb +46 -0
- data/templates/basic/structure.erb +23 -0
- data/test/test_helper.rb +81 -0
- data/test/tests/action_access_test.rb +63 -0
- data/test/tests/actions_test.rb +524 -0
- data/test/tests/authenticators_test.rb +87 -0
- data/test/tests/base_test.rb +35 -0
- data/test/tests/controllers_test.rb +49 -0
- data/test/tests/eval_environment_test.rb +136 -0
- data/test/tests/evel_helpers_test.rb +60 -0
- data/test/tests/examples_test.rb +11 -0
- data/test/tests/helpers_test.rb +97 -0
- data/test/tests/param_set_test.rb +44 -0
- data/test/tests/rack_middleware_test.rb +109 -0
- data/test/tests/request_test.rb +232 -0
- data/test/tests/structures_param_extensions_test.rb +159 -0
- data/test/tests/structures_test.rb +335 -0
- metadata +82 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5af89a4e614e26c289fad12a69304c7a2f293562
|
4
|
+
data.tar.gz: e2db44de7655b8d4d3aa659fd814b1fdca6af522
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90e9f623a0f62e3564fc9ba17850684f263810a04c707ff11765d12522ceef810d8524f8c93b8cc9b3a29e6a72663593552fe6050016c57c7ffe373128969d0c
|
7
|
+
data.tar.gz: 1d661f3d8acc4352ecc936ccbbf42d18afbefcf128db5b993e0c3edd947b84b360af8b6864e21a974b12c29c44be50e04d233c83a46afe1cce7fbd6c62704e8f
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
moonrope (2.0.0)
|
5
|
+
deep_merge (~> 1.0)
|
6
|
+
json (~> 1.7)
|
7
|
+
rack (>= 1.4)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
deep_merge (1.0.1)
|
13
|
+
diff-lcs (1.2.5)
|
14
|
+
json (1.8.3)
|
15
|
+
rack (1.5.2)
|
16
|
+
rack-test (0.6.3)
|
17
|
+
rack (>= 1.0)
|
18
|
+
rake (10.3.1)
|
19
|
+
rspec (0.9.4)
|
20
|
+
rspec-core (3.3.2)
|
21
|
+
rspec-support (~> 3.3.0)
|
22
|
+
rspec-expectations (3.3.1)
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
+
rspec-support (~> 3.3.0)
|
25
|
+
rspec-mocks (3.3.2)
|
26
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
+
rspec-support (~> 3.3.0)
|
28
|
+
rspec-support (3.3.0)
|
29
|
+
test-unit (2.5.5)
|
30
|
+
yard (0.8.7.2)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
moonrope!
|
37
|
+
rack-test (~> 0)
|
38
|
+
rake (~> 10.3)
|
39
|
+
rspec
|
40
|
+
rspec-core
|
41
|
+
rspec-expectations
|
42
|
+
rspec-mocks
|
43
|
+
test-unit (~> 2.5)
|
44
|
+
yard (~> 0.8)
|
45
|
+
|
46
|
+
BUNDLED WITH
|
47
|
+
1.10.6
|
data/MIT-LICENCE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 Adam Cooke.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Moonrope - the self-documenting API
|
2
|
+
|
3
|
+
Moonrope is an API server & client tool for Ruby/Rack applications. It
|
4
|
+
provides everything you need to create an API within your application and
|
5
|
+
have easy to use client libraries provided without any development.
|
6
|
+
|
7
|
+
Not only does it provide a formal structure for your API, it will also generate
|
8
|
+
lovely looking documentation files for develpers to work with.
|
9
|
+
|
10
|
+
This repository is the server-side library which allows you to easily define
|
11
|
+
your API actions & data structures and serve them out using Rack middleware.
|
12
|
+
|
13
|
+
* [Documentation](http://rdoc.info/github/viaduct/moonrope/master/frames)
|
14
|
+
* [Travis CI](https://travis-ci.org/viaduct/moonrope)
|
15
|
+
|
16
|
+

|
17
|
+
|
18
|
+
## Documentation
|
19
|
+
|
20
|
+
* [Introduction](https://github.com/adamcooke/moonrope/blob/master/docs/introduction.md)
|
21
|
+
* [Controllers & Actions](https://github.com/adamcooke/moonrope/blob/master/docs/controllers.md)
|
22
|
+
* [Structures](https://github.com/adamcooke/moonrope/blob/master/docs/structures.md)
|
23
|
+
* [Authentication](https://github.com/adamcooke/moonrope/blob/master/docs/authentication.md)
|
24
|
+
* [Exceptions](https://github.com/adamcooke/moonrope/blob/master/docs/exceptions.md)
|
data/bin/moonrope
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
|
3
|
+
require 'moonrope'
|
4
|
+
require 'moonrope/html_generator'
|
5
|
+
|
6
|
+
if ARGV.size != 2
|
7
|
+
puts "usage: moonrope {path to api} {export dir}"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
|
11
|
+
api_root = ARGV[0]
|
12
|
+
export_dir = ARGV[1]
|
13
|
+
template_name = "basic"
|
14
|
+
|
15
|
+
unless File.directory?(api_root)
|
16
|
+
$stderr.puts "No directory found at #{api_root}"
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
if File.exists?(export_dir) && Dir[File.join(export_dir, '*')].size > 0
|
21
|
+
$stderr.puts "File already exists and is not empty at #{export_dir}. Delete and try again."
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
base = Moonrope::Base.load(ARGV[0])
|
26
|
+
generator = Moonrope::HtmlGenerator.new(base, File.expand_path("../../templates/#{template_name}", __FILE__))
|
27
|
+
generator.generate(export_dir)
|
28
|
+
puts "\e[32mMoonrope documentation generated and saved at #{export_dir}\e[0m"
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Authentication
|
2
|
+
|
3
|
+
Authentication consumers to your API is a key part of developing it. Moonrope's
|
4
|
+
authentication layer provides you with fine grained control of how your authentication
|
5
|
+
works while still maintaining documentability.
|
6
|
+
|
7
|
+
## Getting Started
|
8
|
+
|
9
|
+
To begin, you need to define an `authenticator`. An authenticator's role is to
|
10
|
+
extract an "identity" from an API request. In this example, we'll be authenticating
|
11
|
+
our consumers using a token that will be unique to each user. The example below
|
12
|
+
demonstrates a very simple authenticator.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
authenticator :default do
|
16
|
+
|
17
|
+
header "X-Auth-Token", "The user's unique API token.", :example => 'f29a45f0-b6da-44ae-a029-d4e1744ebaee'
|
18
|
+
|
19
|
+
error 'InvalidAPIToken', "The API token provided in X-Auth-Token was not valid.", :attributes => {:token => "The token that was looked up"}
|
20
|
+
|
21
|
+
lookup do
|
22
|
+
if token = request.headers['X-Auth-Token']
|
23
|
+
if user = User.find_by_api_token(token)
|
24
|
+
user
|
25
|
+
else
|
26
|
+
error 'InvalidAPIToken', :token => token
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
rule :default, "AccessDenied", "Must be authenticated as a user." do
|
32
|
+
identity.is_a?(User)
|
33
|
+
end
|
34
|
+
|
35
|
+
rule :anonymous, "MustBeAnonymous", "Must be anonymous." do
|
36
|
+
identity.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Let's break that down:
|
43
|
+
|
44
|
+
* The first line sets the name of the authenticator. In most cases you'll only
|
45
|
+
ever have one which should be named `:default`. This will apply to all actions
|
46
|
+
in your API.
|
47
|
+
|
48
|
+
* Next, we define a that the authenticator uses the `X-Auth-Token` header. We
|
49
|
+
|
50
|
+
provide a description and example for documentation purposes.
|
51
|
+
* Next, we define that a `InvalidAPIToken` error may be raised when trying to
|
52
|
+
lookup the identity for the request. We include a description plus a hash of
|
53
|
+
attributes that should be returned with the error.
|
54
|
+
|
55
|
+
* Next, we define a `lookup` block which specifies how to lookup your identity
|
56
|
+
object from the request. This is executed in the same scope that would be used
|
57
|
+
for any action in the API. This block will either return the identity object,
|
58
|
+
raise an error or return nothing. If it returns something, that will be used
|
59
|
+
as the identity object and the request will continue. If it raises an error,
|
60
|
+
the error will be returned to the user and the request will stop. It if returns
|
61
|
+
nothing, the request will continue however there will be no identity.
|
62
|
+
|
63
|
+
* Next, we set a default access rule which is executed on every request to
|
64
|
+
verify that the identity has access to the requested action. The `default` rule
|
65
|
+
will apply to all actions however you can create others which can be chosen
|
66
|
+
for specific actions or controllers. The block for this rule must return a true
|
67
|
+
or false value depending on whether the identity satisfies the access condition.
|
68
|
+
The second argument is the error code which will be returned if this condition
|
69
|
+
is not satified for the request. The third argument is the description of the
|
70
|
+
actual condition (for documentation).
|
71
|
+
|
72
|
+
* Finally, we define an anonymous rule which can be used for any actions where there
|
73
|
+
should not be any identity provided.
|
74
|
+
|
75
|
+
## Choosing authenticators & access rules
|
76
|
+
|
77
|
+
When you create actions you can choose which authenticator & access rule should
|
78
|
+
be applied when the action is requested. It's probably easiest to demonstrate
|
79
|
+
this with some code:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
controller :users do
|
83
|
+
|
84
|
+
action :list do
|
85
|
+
# By default, this action will use the 'default' authenticator and the
|
86
|
+
# 'default' access rule.
|
87
|
+
end
|
88
|
+
|
89
|
+
action :colors do
|
90
|
+
# This action will use the 'default' authenticator's anonymous rule.
|
91
|
+
access_rule :anonymous
|
92
|
+
end
|
93
|
+
|
94
|
+
action :create do
|
95
|
+
# This action will use the 'default' access rule on the 'two_factor
|
96
|
+
# authenticator.
|
97
|
+
authenticator :two_factor
|
98
|
+
end
|
99
|
+
|
100
|
+
action :destroy do
|
101
|
+
# This action will use the 'admin' access rule on the 'two_factor'
|
102
|
+
# authenticator.
|
103
|
+
access_rule :two_factor => :admin
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
These different actions all use different rules & authenticators. The generated
|
110
|
+
documentation will reflect these as appropriate.
|
111
|
+
|
112
|
+
It's worth noting that the `authenticator` and `access_rule` methods which are
|
113
|
+
shown here on an action, can also be applied at the controller level and they'll
|
114
|
+
apply to any actions that don't override them.
|
data/docs/controllers.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Controllers & Actions
|
2
|
+
|
3
|
+
Before you can create an action, you'll need to create a controller which can
|
4
|
+
contain your action. In this example, we're going to create an action which
|
5
|
+
will just say "Hello world!".
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
controller :hello do
|
9
|
+
action :world do
|
10
|
+
description "Say hello world"
|
11
|
+
action do
|
12
|
+
"Hello world!"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
This is the most basic definition of a controller & action. It specifies the
|
19
|
+
name of the controller (`hello`) and then adds an action to this controller.
|
20
|
+
This action has the name `world`, a description and a block which specifies
|
21
|
+
what you should be executed when this action is run.
|
22
|
+
|
23
|
+
## Parameters
|
24
|
+
|
25
|
+
It's very common to need to receive additional information when the request
|
26
|
+
is made. Moonrope allows you to receive parameters into your action like
|
27
|
+
normal HTTP requests.
|
28
|
+
|
29
|
+
To access a paremters within your action (or helper), you can simply access
|
30
|
+
it using `params[:name_of_parameter]` or `params.name_of_parameter`. For example,
|
31
|
+
if you wanted to access a parameter named `page` you can use `params[:page]` or
|
32
|
+
`params.page`. If the value sent is nil or an empty string this will return nil.
|
33
|
+
|
34
|
+
### Defining supported parameters
|
35
|
+
|
36
|
+
You can define which parameters are supported for actions. This is an example
|
37
|
+
action which defines some parameters.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
action :say_hello do
|
41
|
+
description "Say some things to a user"
|
42
|
+
param :name, :required => true, :type => String
|
43
|
+
param :age, :required => true, :type => Integer
|
44
|
+
param :hair_color, :type => String, :default => 'Unknown'
|
45
|
+
param :phone_number, :type => String, :regex => /\A\+[\d\s]+\z/
|
46
|
+
action do
|
47
|
+
"Hello #{params.name}! You are #{params.age} and your hair is #{params.hair_color}!"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
When defining a parameter you can define a number of options to assist with
|
53
|
+
validation & default population.
|
54
|
+
|
55
|
+
* `:required => true` - this will require that this parameter is passed with
|
56
|
+
the request. If not an error will be raised before the action is executed.
|
57
|
+
* `:type => String` - this sets what type of object should be submitted. In
|
58
|
+
most cases this should be `String`, `Integer`, `Hash` or `Array`.
|
59
|
+
* `:default => 'Value here'` - sets the default value for the parameter if
|
60
|
+
none is passed.
|
61
|
+
* `:regex => //` - sets a regex which the passed value must conform to
|
62
|
+
|
63
|
+
## Raising errors
|
64
|
+
|
65
|
+
If an action cannot be completed, you'll need to return an error. So that Moonrope
|
66
|
+
can document your API, you need to define all the types of error that an action
|
67
|
+
can return.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
action :show do
|
71
|
+
error "ApplicationSuspended", "The application ({app_name}) you're trying to access is suspended.", :attributes => {:app_name => "The name of the application"}
|
72
|
+
action do
|
73
|
+
if application.suspended?
|
74
|
+
error "ApplicationSuspended", :app_name => application.name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
In this example, you're defining an error called `ApplicationSuspended` along
|
81
|
+
with some information about what it means plus an array of attributes which will
|
82
|
+
be returned with it. When you're actually in the action, you're going to call that
|
83
|
+
error by specifying the name and the attributes to provide. This will then be
|
84
|
+
returned to the user along with the previously defined message and attributes.
|
85
|
+
|
86
|
+
These will always be returned to the consumer with the `error` status.
|
87
|
+
|
88
|
+
## Flags
|
89
|
+
|
90
|
+
Flags contain extra information which you wish to return with your request
|
91
|
+
without interrupting the data you are returning. Think of them like HTTP headers.
|
92
|
+
A use case for these may be that you wish to paginate data and need to return
|
93
|
+
pagination details along with the actual data.
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
action do
|
97
|
+
# Get some pagianted data
|
98
|
+
pages = Page.paginate(:page => params.page)
|
99
|
+
# Set the flags
|
100
|
+
set_flag :current_page, pages.page
|
101
|
+
set_flag :items_per_page, pages.items_per_page
|
102
|
+
set_flag :total_pages, pages.total_pages
|
103
|
+
# Return all the pages as normal
|
104
|
+
pages
|
105
|
+
end
|
106
|
+
```
|
data/docs/exceptions.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Exceptions raised during requests
|
2
|
+
|
3
|
+
Moonrope will rescue any exceptions which are raised during a Rack request.
|
4
|
+
These will be logged to the Moonrope logger and a 500 error will be returned
|
5
|
+
to the client which made the bad request.
|
6
|
+
|
7
|
+
You can also register a callback to be notified whenever an exception is raised
|
8
|
+
in the request lifecycle. This will be useful if you want to report these
|
9
|
+
exceptions to an external exception reporting service.
|
10
|
+
|
11
|
+
This is configured by registering the callback with the Moonrope::Base instance.
|
12
|
+
In a Rails application, this can be added as follows.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
Rails.application.config.moonrope.register_request_error_callback do |request, error|
|
16
|
+
tags = {
|
17
|
+
'component' => 'API'
|
18
|
+
}
|
19
|
+
context = {
|
20
|
+
'controller' => request.controller.try(:name).to_s,
|
21
|
+
'action' => request.action.try(:name).to_s,
|
22
|
+
'identity' => request.identity.is_a?(ActiveRecord::Base) ? "#{request.identity.class}##{request.identity.id}" : request.identity.to_s,
|
23
|
+
'params' => request.params._as_hash
|
24
|
+
}
|
25
|
+
Raven.capture_exception(error, :tags => tags, :extra => context)
|
26
|
+
end
|
27
|
+
```
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# The key components of a Moonrope API
|
2
|
+
|
3
|
+
* **Controller** - a controller is a set of actions which can be executed by
|
4
|
+
users.
|
5
|
+
|
6
|
+
* **Action** - an action is a method which someone can execute on your API. An
|
7
|
+
action may return data, update data or destroy data. Every action returns
|
8
|
+
some data to a user.
|
9
|
+
|
10
|
+
* **Structure** - a structure allows you to convert a Ruby object (in most
|
11
|
+
cases this would be an Active Record model) into a hash which can be returned
|
12
|
+
to a user.
|
13
|
+
|
14
|
+
* **Helper** - a helper is a method which you can define globally or on a
|
15
|
+
per controller basis. A helper can execute code & return objects which you
|
16
|
+
can use in your actions.
|
17
|
+
|
18
|
+
These various components are defined in files which are then loaded by
|
19
|
+
Moonrope automatically when your application starts. In a Rails application,
|
20
|
+
by default these should be placed into a `api` directory in the root of
|
21
|
+
your application. The actual directory structure for your Moonrope API should
|
22
|
+
look like this:
|
23
|
+
|
24
|
+
* `api/controllers` - contains all of your controllers
|
25
|
+
* `api/structures` - contains all your structures
|
26
|
+
* `api/helpers` - contains any helpers you have defined
|
27
|
+
|
28
|
+
The name of files within these folders is not important. All files ending with
|
29
|
+
`.rb` are loaded.
|
data/docs/structures.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# Structures
|
2
|
+
|
3
|
+
A structure is a blueprint outlining out an object can be converted into a hash
|
4
|
+
for output in your API.
|
5
|
+
|
6
|
+
Say, for example, you have a User object and you wish to present this over your
|
7
|
+
API. You would find your user and then pass this through your `user` structure
|
8
|
+
which will return an appropriate hash based on the context the structure is
|
9
|
+
being called from and your authenticated object.
|
10
|
+
|
11
|
+
## Creating a structure
|
12
|
+
|
13
|
+
Structures should be placed into your `structures` directory and best practice
|
14
|
+
dictates they should simply be named `object_structure.rb`, for example a user structure
|
15
|
+
would be named `user_structure.rb`.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
structure :user do
|
19
|
+
|
20
|
+
basic :id, :type => Integer, :example => 1234
|
21
|
+
basic :username, :type => String, :example => "adam"
|
22
|
+
|
23
|
+
full :full_name, :type => String, :example => "Adam"
|
24
|
+
full :last_name, :type => String, :example => "Cooke"
|
25
|
+
full :age, :type => Integer, :example => 27
|
26
|
+
full :created_at, :type => String, :example => "2014-07-01T11:32:59+01:00"
|
27
|
+
full :updated_at, :type => String, :example => "2014-07-01T11:32:59+01:00"
|
28
|
+
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
This example is the most basic way of defining a structure. You see we have defined
|
33
|
+
a number of attributes which should be included in our structure. Basic attributes are
|
34
|
+
always included in the structure whereas full attributes are only included when requested.
|
35
|
+
|
36
|
+
The basic attributes from a structure is often used on its own when referenced
|
37
|
+
from other structures. For example, if users had many projects, the project
|
38
|
+
structure may reference user but only request the basic information as any further
|
39
|
+
information is not needed.
|
40
|
+
|
41
|
+
The full attributes would be returned when listing a specific user or a list
|
42
|
+
of users on their own. For example, your users/list or users/info methods would
|
43
|
+
likely return full information rather than just the basic.
|
44
|
+
|
45
|
+
Note that when full information is requested, it is always combined with the
|
46
|
+
information from basic so there's no need to duplicate attribute definitions.
|
47
|
+
|
48
|
+
### Mapping
|
49
|
+
|
50
|
+
Any attributes which you add to your structure are mapped one to one with the attributes
|
51
|
+
available on the source object. If the name doesn't match, you can use the `:name`
|
52
|
+
option to set the actual name of the attribute on the source object.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
basic :user_id, :source_attribute => :id
|
56
|
+
```
|
57
|
+
|
58
|
+
Alternatively, you can specify a block which will be used when mapping the value to the
|
59
|
+
correct object.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
basic :name_in_caps, :value => Proc.new { o.name.upcase }
|
63
|
+
```
|
64
|
+
|
65
|
+
### Grouping
|
66
|
+
|
67
|
+
Attributes can be placed into groups which will return a hash containing all items
|
68
|
+
within the group.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
group :financials do
|
72
|
+
basic :balance, :type => Integer, :example => 12345
|
73
|
+
full :last_invoice_raised_at, :type => String, :example => ""
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
### Expansions
|
78
|
+
|
79
|
+
An expansion allows you to manually define extra information which can be
|
80
|
+
returned with your user object. For example, in some API methods you may wish
|
81
|
+
to return extra information about the user's current financial status which
|
82
|
+
isn't usually returned.
|
83
|
+
|
84
|
+
In most cases, an expansion will be a link to another structure which you have
|
85
|
+
defined. An expansion can be defined as shown below:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
# A single object which is associated with your user (belongs to)
|
89
|
+
expansion :currency, :type => Hash, :structure => :currency
|
90
|
+
# or including an array of objects (has many)
|
91
|
+
expansion :projects, :type => Array, :structure => :project
|
92
|
+
```
|
93
|
+
|
94
|
+
The same `:structure => :name` can be used on any attribute which you define in your
|
95
|
+
structure. Therefore, if you need to always include a structure, you can simply
|
96
|
+
add it to a full or basic line.
|
97
|
+
|
98
|
+
### Conditional attributes
|
99
|
+
|
100
|
+
You can specify a condition on any attribute or expansion. This can be done by passing
|
101
|
+
a block to the `:if` option when defining an attribute.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
condition Proc.new { identity.is_super_special_admin? }, "Only available to special admins" do
|
105
|
+
basic :pin
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
As well as specifying inline blocks, you can also reference access rules from any
|
110
|
+
of your authenticators.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
condition :default => :admins do
|
114
|
+
basic :pin
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
This will use the `admins` rule on the `default` authenticator to verify whether
|
119
|
+
or not the `pin` attribute will be displayed.
|
120
|
+
|
121
|
+
If you're only using your default authenticator, you can omit the authenticator
|
122
|
+
name in this call. For example:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
condition :admins do
|
126
|
+
basic :pin
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
## Accessing structures from actions
|
131
|
+
|
132
|
+
Now... how do you include structures from within an action I hear you ask.
|
133
|
+
That's actually pretty simple. Throughout both actions and structures, you can
|
134
|
+
use the `structure` method to load a structure's hash. Here are a number of
|
135
|
+
examples which you can use to load a hash from a structure.
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# Return the basic information for a user
|
139
|
+
structure(:user, user)
|
140
|
+
|
141
|
+
# Return the full information for a user
|
142
|
+
structure(:user, user, :full => true)
|
143
|
+
|
144
|
+
# Return the full information plus all expansions
|
145
|
+
structure(:user, user, :full => true, :expansions => true)
|
146
|
+
|
147
|
+
# Return the full information plus specified expansions
|
148
|
+
structure(:user, user, :full => true, :expansions => [:projects, :financials])
|
149
|
+
|
150
|
+
# You don't need to provide the name of the structure if it can
|
151
|
+
# be auto-determined from the name of the class.
|
152
|
+
structure(user, :full => true)
|
153
|
+
```
|
154
|
+
|
155
|
+
Remember, these can be used in structures as well as actions. So, you may
|
156
|
+
want to create expansions which link to other structures. For example, if you
|
157
|
+
wanted your user hash to include a list of associated projects:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
expansion :projects do
|
161
|
+
o.projects.map { |p| structure(:project, p) }
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
If you wish to check whether or not a structure exists before calling it from
|
166
|
+
an action, you can use the `has_structure_for?` method as shown below.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
if has_structure_for?(:user)
|
170
|
+
structure(:user, user)
|
171
|
+
else
|
172
|
+
error :error, "Structure not found."
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
In some cases, you may wish for your consumers to decide which expansions
|
177
|
+
should be returned. This can be achieved by passing `:paramable` when calling
|
178
|
+
a `structure` from an action. For example:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
# Allow consumer to choose from any expansion registered on the structure.
|
182
|
+
# (taking into account any conditions you specify) and choose whether or not
|
183
|
+
# to include `full` attributes.
|
184
|
+
structure(user, :paramable => true)
|
185
|
+
|
186
|
+
# Allow the consumer to choose any expansions but not allow control over whether
|
187
|
+
# full attributes should be returned. Returns all expansions by default.
|
188
|
+
structure(user, :paramable => {:expansions => true})
|
189
|
+
|
190
|
+
# Allow the consumer to choose any expansions but not allow control over whether
|
191
|
+
# full attributes should be returned. Returns no expansions by default.
|
192
|
+
structure(user, :paramable => {:expansions => false})
|
193
|
+
|
194
|
+
# Allow consumer to choose from any expansions you list. By default, the items
|
195
|
+
# you list will be included in the response if the user does not request any
|
196
|
+
# specific expansions.
|
197
|
+
structure(user, :paramable => {:expansions => [:server, :endpoint]})
|
198
|
+
|
199
|
+
# Allow the consumer to control whether or not full attributes should be returned
|
200
|
+
# in the structure or not but do not allow any control of expansions.
|
201
|
+
structure(user, :paramable => {:full => true})
|
202
|
+
|
203
|
+
# Allow the consumer to control whether or not full attributes should be returned
|
204
|
+
# but do not return them by default.
|
205
|
+
structure(user, :paramable => {:full => false})
|
206
|
+
```
|
207
|
+
|
208
|
+
To access these, consumers should send an `_expansions` param with their request which
|
209
|
+
should contain an array containing the names of the expansions that should be
|
210
|
+
included. Consumers can also send `true` or `false` in this parameter to return
|
211
|
+
all or no expansions.
|
212
|
+
|
213
|
+
To control access to `full` attributes, consumers should pass `_full` parameter
|
214
|
+
as true or false.
|