introspective_grape 0.1.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/README.md +61 -9
- data/lib/introspective_grape/api.rb +4 -1
- data/lib/introspective_grape/filters.rb +26 -2
- data/lib/introspective_grape/version.rb +1 -1
- data/spec/requests/user_api_spec.rb +0 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2f679318dbf2d22e2af0c95708eee64adf103fb
|
4
|
+
data.tar.gz: 786a80ed73eec2168392904e459266dda8b22663
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ebf9eb5fd7859107a9d412cfe09728eb839191167b46cbd6d85ff9b1d740764cddc3acb621488c66e6b2d1a659f29c0b012c92948a35a478509e53012a5a5de
|
7
|
+
data.tar.gz: 9c8b93a2952bb15a2b8ba751efb1d49d00156a4267988068f91ce81ab71dad6860281b4d21c9d3424e6b162fd75fed26e835dbdf3e4d696d973e08700773ea1e
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,31 @@
|
|
1
1
|
|
2
|
+
0.2.0 10/12/2016
|
3
|
+
==============
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
Allow index action filter overrides if there is a class-level 'assignment' method on the field
|
8
|
+
|
9
|
+
Allow custom filters, e.g.:
|
10
|
+
|
11
|
+
class MyAPI < IntrospectiveGrape::API
|
12
|
+
custom_filter :my_filter, {type: Boolean, description: "Filter on some scope" }
|
13
|
+
|
14
|
+
restful MyModel, [my_field]
|
15
|
+
end
|
16
|
+
|
17
|
+
class MyModel
|
18
|
+
self << class
|
19
|
+
def my_filter(filter=false)
|
20
|
+
filter ? my_scope : where(nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
def my_field=(parameters)
|
24
|
+
# parse the passed parameters in some fancy way and return a query scope
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
2
29
|
0.1.9 9/27/2016
|
3
30
|
==============
|
4
31
|
### Features
|
data/README.md
CHANGED
@@ -77,7 +77,7 @@ In app/api/v1/my_model_api.rb:
|
|
77
77
|
|
78
78
|
```
|
79
79
|
class MyModelAPI < IntrospectiveGrape::API
|
80
|
-
skip_presence_validations :
|
80
|
+
skip_presence_validations :attribute_with_generated_default_value
|
81
81
|
exclude_actions Model, <:index,:show,:create,:update,:destroy>
|
82
82
|
default_includes Model, <associations for eager loading>
|
83
83
|
|
@@ -86,7 +86,7 @@ class MyModelAPI < IntrospectiveGrape::API
|
|
86
86
|
|
87
87
|
paginate per_page 25, offset: 0, max_per_page: false
|
88
88
|
|
89
|
-
restful MyModel, [:strong, :param, :fields, :and, {
|
89
|
+
restful MyModel, [:strong, :param, :fields, :and, { nested_model_attributes: [:nested,:fields, :_destroy] }] do
|
90
90
|
# Add additional end points to the model's namespace
|
91
91
|
end
|
92
92
|
|
@@ -111,7 +111,9 @@ types for the attributes specified in a hash, e.g.:
|
|
111
111
|
|
112
112
|
```
|
113
113
|
def self.attribute_param_types
|
114
|
-
{ "<attribute name>" =>
|
114
|
+
{ "<attribute name 1>" => String,
|
115
|
+
"<attribute name 2>" => Integer,
|
116
|
+
"<attribute name 3>" => Virtus::Attribute::Boolean }
|
115
117
|
end
|
116
118
|
```
|
117
119
|
|
@@ -120,9 +122,57 @@ nested params as well as nested routes will be declared, allowing for
|
|
120
122
|
a good deal of flexibility for API consumers out of the box, such as implicitly
|
121
123
|
creating bulk update endpoints for nested models.
|
122
124
|
|
125
|
+
|
126
|
+
## Filtering and Searching
|
127
|
+
|
128
|
+
IntrospectiveGrape will automatically generate and parse filters on all exposed fields in the API.
|
129
|
+
|
130
|
+
Multiple values can be specified at once for Integer attributes that end in "id" (i.e. conventional primary and foreign keys) at once by passing a comma separated list of IDs.
|
131
|
+
|
132
|
+
For timestamp attributes it will generate `<name_of_timestamp>_start` and `<name_of_timestamp>_end` constraints.
|
133
|
+
|
134
|
+
### Overriding Filter Queries
|
135
|
+
|
136
|
+
If, e.g., a field is some sort of complex composite rather than a simple field value you can override the default behavior (`where(field: params[field])`) by adding a query method on the model class:
|
137
|
+
|
138
|
+
```
|
139
|
+
class MyAPI < IntrospectiveGrape::API
|
140
|
+
restful MyModel, [my_composite_field]
|
141
|
+
end
|
142
|
+
|
143
|
+
class MyModel
|
144
|
+
self << class
|
145
|
+
def my_composite_field=(parameters)
|
146
|
+
# parse the passed parameters in some way and return a query scope
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### Custom Filter Methods
|
153
|
+
|
154
|
+
To add a custom filter to the index action you can declare a method to be called
|
155
|
+
against the model class with `custom_filter`. You can pass documentation and type
|
156
|
+
constraints (it would default to String) and other Grape parameter options in a hash:
|
157
|
+
|
158
|
+
```
|
159
|
+
class MyAPI < IntrospectiveGrape::API
|
160
|
+
custom_filter :my_filter, type: Boolean, description: "Filter on some scope"
|
161
|
+
end
|
162
|
+
|
163
|
+
class MyModel
|
164
|
+
self << class
|
165
|
+
def my_filter(filter=false)
|
166
|
+
filter ? my_scope : where(nil)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
|
123
173
|
## Pagination
|
124
174
|
|
125
|
-
The index action by default will not be paginated, simply declared `paginate` before the `restful` declaration will enable [Kaminari](https://github.com/amatsuda/kaminari) pagination on the index results using a default 25 results per page with an offset of 0.
|
175
|
+
The index action by default will not be paginated, simply declared `paginate` before the `restful` declaration will enable [Kaminari](https://github.com/amatsuda/kaminari) pagination on the index results using a default 25 results per page with an offset of 0. You can pass Kaminari's options to the paginate declaration, `per_page`, `max_per_page`, etc.
|
126
176
|
|
127
177
|
## Excluding Endpoints
|
128
178
|
|
@@ -136,7 +186,7 @@ declarations on the model. You can also include or exclude :all or :none as shor
|
|
136
186
|
Grape only applies hooks in the order they were declared, so to hook into the default
|
137
187
|
RESTful actions defined by IntrospectiveGrape you need to declare any hooks before the
|
138
188
|
`restful` declaration, rather than inside its block, where the hook will only apply to
|
139
|
-
subsequently declared endpoints.
|
189
|
+
your own subsequently declared endpoints.
|
140
190
|
|
141
191
|
|
142
192
|
## Dependencies
|
@@ -146,11 +196,13 @@ Tool | Description
|
|
146
196
|
[Grape] | An opinionated micro-framework for creating REST-like APIs in Ruby
|
147
197
|
[GrapeEntity] | Adds Entity support to API frameworks, such as Grape.
|
148
198
|
[GrapeSwagger] | Swagger docs.
|
199
|
+
[GrapeKaminari] | Pagination.
|
149
200
|
[Pundit] | Minimal authorization through OO design and pure Ruby classes
|
150
201
|
|
151
|
-
[Grape]:
|
152
|
-
[GrapeEntity]:
|
153
|
-
[GrapeSwagger]:
|
154
|
-
[
|
202
|
+
[Grape]: https://github.com/ruby-grape/grape
|
203
|
+
[GrapeEntity]: https://github.com/ruby-grape/grape-entity
|
204
|
+
[GrapeSwagger]: https://github.com/ruby-grape/grape-swagger
|
205
|
+
[GrapeKaminari]: https://github.com/monterail/grape-kaminari
|
206
|
+
[Pundit]: https://github.com/elabs/pundit
|
155
207
|
|
156
208
|
|
@@ -28,7 +28,9 @@ module IntrospectiveGrape
|
|
28
28
|
# types for the attributes specified in a hash:
|
29
29
|
#
|
30
30
|
# def self.attribute_param_types
|
31
|
-
# { "<attribute name>" => Virtus::Attribute::Boolean
|
31
|
+
# { "<attribute name>" => Virtus::Attribute::Boolean,
|
32
|
+
# "<attribute name>" => Integer,
|
33
|
+
# "<attribute name>" => String }
|
32
34
|
# end
|
33
35
|
#
|
34
36
|
# For nested models declared in Rails' strong params both the Grape params for the
|
@@ -88,6 +90,7 @@ module IntrospectiveGrape
|
|
88
90
|
routes = build_routes(routes, model)
|
89
91
|
define_routes(routes, whitelist)
|
90
92
|
|
93
|
+
# Top level declaration of the Grape::API namespace for the resource:
|
91
94
|
resource routes.first.name.pluralize do
|
92
95
|
# yield to append additional routes under the root namespace
|
93
96
|
yield if block_given?
|
@@ -1,7 +1,19 @@
|
|
1
1
|
module IntrospectiveGrape::Filters
|
2
2
|
#
|
3
|
-
# Allow filters on all whitelisted model attributes (from api_params)
|
3
|
+
# Allow filters on all whitelisted model attributes (from api_params) and declare
|
4
|
+
# customer filters for the index in a method.
|
4
5
|
#
|
6
|
+
|
7
|
+
def custom_filter(*args)
|
8
|
+
custom_filters( *args )
|
9
|
+
end
|
10
|
+
|
11
|
+
def custom_filters(*args)
|
12
|
+
@custom_filters ||= {}
|
13
|
+
@custom_filters = Hash[*args].merge(@custom_filters) if args.present?
|
14
|
+
@custom_filters
|
15
|
+
end
|
16
|
+
|
5
17
|
def simple_filters(klass, model, api_params)
|
6
18
|
@simple_filters ||= api_params.select {|p| p.is_a? Symbol }.map { |field|
|
7
19
|
(klass.param_type(model,field) == DateTime ? ["#{field}_start", "#{field}_end"] : field.to_s)
|
@@ -33,11 +45,16 @@ module IntrospectiveGrape::Filters
|
|
33
45
|
terminal = field.ends_with?("_start") ? "initial" : "terminal"
|
34
46
|
dsl.optional field, type: klass.param_type(model,field), description: "Constrain #{field} by #{terminal} date."
|
35
47
|
elsif identifier_filter(klass,model,field)
|
36
|
-
dsl.optional field, type: Array[Integer], coerce_with: ->(val) { val.split(',') }
|
48
|
+
dsl.optional field, type: Array[Integer], coerce_with: ->(val) { val.split(',') }, description: "Filter by a comma separated list of integers."
|
37
49
|
else
|
38
50
|
dsl.optional field, type: klass.param_type(model,field), description: "Filter on #{field} by value."
|
39
51
|
end
|
40
52
|
end
|
53
|
+
|
54
|
+
custom_filters.each do |filter,details|
|
55
|
+
dsl.optional filter, details
|
56
|
+
end
|
57
|
+
|
41
58
|
dsl.optional :filter, type: String, description: "JSON of conditions for query. If you're familiar with ActiveRecord's query conventions you can build more complex filters, e.g. against included child associations, e.g. {\"<association_name>_<parent>\":{\"field\":\"value\"}}"
|
42
59
|
|
43
60
|
end
|
@@ -49,11 +66,18 @@ module IntrospectiveGrape::Filters
|
|
49
66
|
if timestamp_filter(klass,model,field)
|
50
67
|
op = field.ends_with?("_start") ? ">=" : "<="
|
51
68
|
records = records.where("#{timestamp_filter(klass,model,field)} #{op} ?", Time.zone.parse(params[field]))
|
69
|
+
elsif model.respond_to?("#{field}=")
|
70
|
+
records = records.send("#{field}=", params[field])
|
52
71
|
else
|
53
72
|
records = records.where(field => params[field])
|
54
73
|
end
|
55
74
|
end
|
56
75
|
|
76
|
+
klass.custom_filters.each do |filter,details|
|
77
|
+
records = records.send(filter, params[filter])
|
78
|
+
end
|
79
|
+
|
80
|
+
|
57
81
|
if params[:filter].present?
|
58
82
|
filters = JSON.parse( params[:filter].delete('\\') )
|
59
83
|
filters.each do |key, value|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: introspective_grape
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Buermann
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|