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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b9618a1c95e98590a625b563d4abd19954fd07fb
4
- data.tar.gz: c8482ddba2d3dcacc302aee77420520c62823921
3
+ metadata.gz: d2f679318dbf2d22e2af0c95708eee64adf103fb
4
+ data.tar.gz: 786a80ed73eec2168392904e459266dda8b22663
5
5
  SHA512:
6
- metadata.gz: 2522acc99b5c1a713ba0a254b00f586cac962c5ab777a6d11ac36b551700b9f3c8b7802f9dfbc23b20eddedfc05eafd53f4d6435a7a6fdbd58e266daa7f1582a
7
- data.tar.gz: be83fbdf2ac3d282ab542915f8b1202223e4895b75d166cbf1ee46c3ff4a848e86b109ab2e26e8897e75b9e674b8b6c4cbf875f2d97cc962789f24bb948881f5
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 :attribute
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, { nested_attributes: [:nested,:fields, :_destroy] }] do
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>" => Virtus::Attribute::Boolean }
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]: https://github.com/ruby-grape/grape
152
- [GrapeEntity]: https://github.com/ruby-grape/grape-entity
153
- [GrapeSwagger]: https://github.com/ruby-grape/grape-swagger
154
- [Pundit]: https://github.com/elabs/pundit
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|
@@ -1,3 +1,3 @@
1
1
  module IntrospectiveGrape
2
- VERSION = "0.1.9".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
@@ -44,7 +44,6 @@ describe Dummy::UserAPI, type: :request do
44
44
  response.should be_success
45
45
  json.length.should eq 3
46
46
  json.map {|j| j['id'] }.should eq user_ids
47
-
48
47
  end
49
48
 
50
49
  it "should not expose users' encrypted_passwords" do
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.1.9
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-09-27 00:00:00.000000000 Z
11
+ date: 2016-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails