introspective_grape 0.1.9 → 0.2.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/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
|