api_presenter 0.2.4 → 0.3.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/README.md +80 -13
- data/lib/api_presenter.rb +2 -0
- data/lib/api_presenter/base.rb +3 -3
- data/lib/api_presenter/configuration.rb +43 -0
- data/lib/api_presenter/version.rb +1 -1
- data/lib/generators/api_presenter/config/USAGE +1 -0
- data/lib/generators/api_presenter/config/config_generator.rb +11 -0
- data/lib/generators/api_presenter/config/templates/api_presenter_config.rb +5 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e411385a8bd85df6bc3efe9f1ec418d5608f26f
|
4
|
+
data.tar.gz: a7da14574b9aabd4501edafd1555feb16246ff05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 810bfdd0b1932949b0d6984ac1b0935467de3e5d27858f05890b906447aa2124fc0beee23c0cf95a5d624df168a2e20eb2b38d567650c685e291a905724859a4
|
7
|
+
data.tar.gz: 29e3508ed61708ae44f7f4470e9687a0131d0e56a991b307ba1519ebd6b6755d92ff7903a26f7af6d25b6bd78d8f62b4a742332439d5b9b758f6bfc08e65f583
|
data/README.md
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
A much longer readme is coming, including best practices and cautions, but in the meantime lets keep it simple...
|
4
4
|
|
5
|
-
When creating RESTful APIs for web or mobile clients, there are a couple of
|
5
|
+
When creating RESTful APIs for web or mobile clients, there are a couple of desirable endpoint behaviors:
|
6
6
|
|
7
|
-
* Allow inclusion of associated data to mitigate number of requests
|
8
7
|
* Include permissions so that the client can intelligently draw its UI (ex: edit/delete buttons), while maintaining a single source of truth
|
8
|
+
* Allow inclusion of associated data to mitigate total number of requests
|
9
9
|
|
10
10
|
ApiPresenter does both of these things, plus a bit more.
|
11
11
|
|
@@ -52,7 +52,11 @@ class User < ActiveRecord::Base
|
|
52
52
|
end
|
53
53
|
```
|
54
54
|
|
55
|
-
|
55
|
+
### 0. Generate config file
|
56
|
+
|
57
|
+
`rails g api_presenter:config`
|
58
|
+
|
59
|
+
Generates a configuration file that allows you to override the default querystring params used by the `presenter` concern. More to come.
|
56
60
|
|
57
61
|
### 1. Create your Presenter
|
58
62
|
|
@@ -78,13 +82,19 @@ end
|
|
78
82
|
|
79
83
|
Presenters can define up to three methods:
|
80
84
|
|
81
|
-
* `associations_map` The includable resources for the ActiveRecord model (`Post`, in this case). Consists of the model name as key and traversal required to preload/load them. In most cases, the value of `associations` will correspond directly to associations on the primary model.
|
82
|
-
* `policy_methods` A list of Pundit policy methods to resolve for the primary collection.
|
85
|
+
* `associations_map` The business-dictated includable resources for the ActiveRecord model (`Post`, in this case). Consists of the model name as key and traversal required to preload/load them. In most cases, the value of `associations` will correspond directly to associations on the primary model.
|
86
|
+
* `policy_methods` A list of Pundit policy methods to resolve for the primary collection if policies are requested.
|
83
87
|
* `policy_associations` Additional records to preload in order to optimize policies that must traverse asscoiations.
|
84
88
|
|
85
89
|
### 2. Enable your controllers
|
86
90
|
|
87
|
-
Your presentable collection can be an `ActiveRecord::Relation`, an array of records, or even a single record. Just call `present` on it. The preloads will be performed, and the included collections/policies will be available in the `@presenter` instance variable.
|
91
|
+
Your presentable collection can be an `ActiveRecord::Relation`, an array of records, or even a single record. Just call `present` on it from your controller action. The preloads will be performed, and the included collections/policies will be available in the `@presenter` instance variable.
|
92
|
+
|
93
|
+
The following querystring params are used by the supplied controller concern's `present` method:
|
94
|
+
|
95
|
+
* `count [Boolean]` Pass true if you just want a count of the primary collection
|
96
|
+
* `policies [Boolean]` Pass true if you want to resolve policies for the primary collection records
|
97
|
+
* `include [String, Array]` A comma-delimited list or array of collection names (camelCase or under_scored) to include with the primary collection
|
88
98
|
|
89
99
|
```ruby
|
90
100
|
class ApplicationController
|
@@ -92,11 +102,18 @@ class ApplicationController
|
|
92
102
|
end
|
93
103
|
|
94
104
|
class PostsController < ApplicationController
|
105
|
+
|
106
|
+
# @example
|
107
|
+
# GET /posts?include=categories,subCategories,users&policies=true
|
108
|
+
#
|
95
109
|
def index
|
96
110
|
posts = PostQuery.records(current_user, params)
|
97
111
|
present posts
|
98
112
|
end
|
99
113
|
|
114
|
+
# @example
|
115
|
+
# GET /posts/:id?include=categories,subCategories,users&policies=true
|
116
|
+
#
|
100
117
|
def show
|
101
118
|
post = Post.find(params[:id])
|
102
119
|
present post
|
@@ -134,13 +151,6 @@ end
|
|
134
151
|
json.partial!("api/shared/included_collections_and_meta", presenter: @presenter)
|
135
152
|
```
|
136
153
|
|
137
|
-
```ruby
|
138
|
-
json.posts(@presenter.collection) do |post|
|
139
|
-
json.partial!(post)
|
140
|
-
end
|
141
|
-
json.partial!("api/shared/included_collections_and_meta", presenter: @presenter)
|
142
|
-
```
|
143
|
-
|
144
154
|
### api/shared/included_collections_and_meta
|
145
155
|
|
146
156
|
```ruby
|
@@ -156,6 +166,62 @@ json.meta do
|
|
156
166
|
end
|
157
167
|
```
|
158
168
|
|
169
|
+
### 4. Output
|
170
|
+
|
171
|
+
Using the code above, our call to `GET /posts` would result in the following JSON:
|
172
|
+
|
173
|
+
```json
|
174
|
+
{
|
175
|
+
"posts": [
|
176
|
+
{ "id": 1, "sub_category": 1, "creator_id": 1, "publisher_id": 2, "body": "Lorem dim sum", "published": true },
|
177
|
+
{ "id": 2, "sub_category": 2, "creator_id": 3, "publisher_id": null, "body": "Lorem dim sum", "published": false }
|
178
|
+
],
|
179
|
+
"categories": [
|
180
|
+
{ "id": 1, "name": "Animals" }
|
181
|
+
],
|
182
|
+
"sub_categories": [
|
183
|
+
{ "id": 1, "category_id": 1, "name": "Lemurs" },
|
184
|
+
{ "id": 2, "category_id": 1, "name": "Anteaters" }
|
185
|
+
],
|
186
|
+
"users": [
|
187
|
+
{ "id": 1, "name": "Dora" },
|
188
|
+
{ "id": 2, "name": "Boots" },
|
189
|
+
{ "id": 3, "name": "Backpack" }
|
190
|
+
],
|
191
|
+
"meta": {
|
192
|
+
"total_count": 2,
|
193
|
+
"policies": [
|
194
|
+
{ "post_id": 1, "update": true, "destroy": false },
|
195
|
+
{ "post_id": 2, "update": true, "destroy": true }
|
196
|
+
]
|
197
|
+
}
|
198
|
+
}
|
199
|
+
```
|
200
|
+
|
201
|
+
And similarily, for `GET /posts/1`:
|
202
|
+
|
203
|
+
```json
|
204
|
+
{
|
205
|
+
"post": { "id": 1, "sub_category": 1, "creator_id": 1, "publisher_id": 2, "body": "Lorem dim sum", "published": true },
|
206
|
+
"categories": [
|
207
|
+
{ "id": 1, "name": "Animals" }
|
208
|
+
],
|
209
|
+
"sub_categories": [
|
210
|
+
{ "id": 1, "category_id": 1, "name": "Lemurs" }
|
211
|
+
],
|
212
|
+
"users": [
|
213
|
+
{ "id": 1, "name": "Dora" },
|
214
|
+
{ "id": 2, "name": "Boots" }
|
215
|
+
],
|
216
|
+
"meta": {
|
217
|
+
"total_count": 1,
|
218
|
+
"policies": [
|
219
|
+
{ "post_id": 1, "update": true, "destroy": false }
|
220
|
+
]
|
221
|
+
}
|
222
|
+
}
|
223
|
+
```
|
224
|
+
|
159
225
|
## Advanced Usage
|
160
226
|
|
161
227
|
### Conditional includes
|
@@ -242,6 +308,7 @@ end
|
|
242
308
|
* Make index policy checking on includes optional
|
243
309
|
* Allow custom collection names
|
244
310
|
* Add test helper to assert presenter was called for a given controller action
|
311
|
+
* Add presenter generator
|
245
312
|
|
246
313
|
## Development
|
247
314
|
|
data/lib/api_presenter.rb
CHANGED
data/lib/api_presenter/base.rb
CHANGED
@@ -80,7 +80,7 @@ module ApiPresenter
|
|
80
80
|
# @return [Array<Symbol>]
|
81
81
|
#
|
82
82
|
def included_collection_names
|
83
|
-
@included_collection_names ||= Parsers::ParseIncludeParams.call(params[
|
83
|
+
@included_collection_names ||= Parsers::ParseIncludeParams.call(params[ApiPresenter.configuration.include_param])
|
84
84
|
end
|
85
85
|
|
86
86
|
# Map of included collection names and loaded record
|
@@ -172,11 +172,11 @@ module ApiPresenter
|
|
172
172
|
private
|
173
173
|
|
174
174
|
def count_only?
|
175
|
-
@count_only ||= !!params[
|
175
|
+
@count_only ||= !!params[ApiPresenter.configuration.count_param]
|
176
176
|
end
|
177
177
|
|
178
178
|
def resolve_policies?
|
179
|
-
@resolve_policies ||= current_user && !!params[
|
179
|
+
@resolve_policies ||= current_user && !!params[ApiPresenter.configuration.policies_param]
|
180
180
|
end
|
181
181
|
|
182
182
|
def resolve_included_collctions?
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ApiPresenter
|
2
|
+
class Configuration
|
3
|
+
# Querystring param key that determines whether or not to just produce a total count
|
4
|
+
#
|
5
|
+
# @return [Symbol]
|
6
|
+
#
|
7
|
+
attr_accessor :count_param
|
8
|
+
|
9
|
+
# Querystring param key containing the included collection names
|
10
|
+
#
|
11
|
+
# @return [Symbol]
|
12
|
+
#
|
13
|
+
attr_accessor :include_param
|
14
|
+
|
15
|
+
# Querystring param key that determines whether or not to resolve policies for primary collection
|
16
|
+
#
|
17
|
+
# @return [Symbol]
|
18
|
+
#
|
19
|
+
attr_accessor :policies_param
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@count_param = :count
|
23
|
+
@include_param = :include
|
24
|
+
@policies_param = :policies
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [ApiPresenter::Configuration] ApiPresenter's current configuration
|
29
|
+
def self.configuration
|
30
|
+
@configuration ||= Configuration.new
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set ApiPresenter configuration
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# ApiPresenter.configure do |config|
|
37
|
+
# config.include_param = :includes
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
def self.configure
|
41
|
+
yield configuration
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Creates configuration file
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ApiPresenter
|
2
|
+
module Generators
|
3
|
+
class ConfigGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
def copy_config_file
|
7
|
+
copy_file 'api_presenter_config.rb', 'config/initializers/api_presenter_config.rb'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_presenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yuval Kordov
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-11-
|
12
|
+
date: 2016-11-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -131,11 +131,15 @@ files:
|
|
131
131
|
- lib/api_presenter.rb
|
132
132
|
- lib/api_presenter/base.rb
|
133
133
|
- lib/api_presenter/concerns/presentable.rb
|
134
|
+
- lib/api_presenter/configuration.rb
|
134
135
|
- lib/api_presenter/parsers/parse_include_params.rb
|
135
136
|
- lib/api_presenter/resolvers/base.rb
|
136
137
|
- lib/api_presenter/resolvers/included_collections_resolver.rb
|
137
138
|
- lib/api_presenter/resolvers/policies_resolver.rb
|
138
139
|
- lib/api_presenter/version.rb
|
140
|
+
- lib/generators/api_presenter/config/USAGE
|
141
|
+
- lib/generators/api_presenter/config/config_generator.rb
|
142
|
+
- lib/generators/api_presenter/config/templates/api_presenter_config.rb
|
139
143
|
homepage: http://github.com/uberllama/api_presenter
|
140
144
|
licenses:
|
141
145
|
- MIT
|