restful_json 3.4.2 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +656 -453
- data/Rakefile +1 -1
- data/lib/restful_json.rb +0 -4
- data/lib/restful_json/config.rb +58 -14
- data/lib/restful_json/controller.rb +132 -67
- data/lib/restful_json/default_controller.rb +9 -1
- data/lib/restful_json/version.rb +1 -1
- metadata +2 -48
- data/lib/restful_json/base_controller.rb +0 -13
- data/lib/restful_json/railtie.rb +0 -19
- data/lib/twinturbo/application_permitter.rb +0 -93
- data/lib/twinturbo/controller.rb +0 -43
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZjYzYjM4ODY4NjIyNWM4ZDdjMGQxODY0M2QyMGM5Y2MzMTVlMjFjZg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MjA0NzNiYzc0OTJiMjU0OGE3ZjQ4ZWU5YmUyZTJhMTliYjdlZDg0ZA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YjIxMjNjYTFhZmM5M2E4Yjg3Mzc4MmMzZDA3MTIyMTgxMzYyZjBjZTRmODJi
|
10
|
+
YjgyOWI3M2EzMGIzNGI4ZmI5YzI3ODRmZDBiMDc1NGMxY2IzNTBmYTFmMmEy
|
11
|
+
ZjAzZmRlMjAzMWIyYWRmODVlYmFlNzhiODg0YTBmMWQ1Y2JiNTI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NDJlYjExZThhNzU0OWJmYmJiOTQ3YTI3ZmQ0NDZhNDU4YmQxODYzYTk2ODBi
|
14
|
+
NzgzNGZlOGJkMzZlMGJmODU3NDdkN2JhOTkxZDljZjc2NWE5ODliZTk4YjEy
|
15
|
+
NzM0NzFiODEzZDNhNWM1ZDdiNjdjNGEwOTBmZjNjYzY4NGUzNjk=
|
data/README.md
CHANGED
@@ -1,265 +1,331 @@
|
|
1
|
-
|
1
|
+
[![Build Status](https://secure.travis-ci.org/rubyservices/restful_json.png?branch=master)](http://travis-ci.org/rubyservices/restful_json) [![Gem Version](https://badge.fury.io/rb/restful_json.png)](http://badge.fury.io/rb/restful_json)
|
2
|
+
# Restful JSON
|
2
3
|
|
3
|
-
Develop declarative, featureful
|
4
|
+
Develop declarative, featureful JSON service controllers to use with modern Javascript MVC frameworks like AngularJS, Ember, etc. with much less code. It is RESTful-ish instead of RESTful, since it isn't hypermedia-driven, but it meets the long-standing Rails definition of being RESTful.
|
4
5
|
|
5
6
|
What does that mean? It means you typically won't have to write index, create, update, destroy, etc. methods in your controllers to filter, sort, and do complex queries.
|
6
7
|
|
7
|
-
Why do you need this if Rails controllers already make it easy to provide RESTful JSON services via generated controllers? Because this is just as flexible, almost as declarative, and takes less code.
|
8
|
+
Why do you need this if Rails controllers already make it easy to provide RESTful JSON services via generated controllers? Because this is just as flexible, almost as declarative, and takes less code. Your controllers will be easier to read, and there will be less code to maintain. When you need an action method more customized, that method is all you will have to write.
|
8
9
|
|
9
10
|
The goal of the project is to reduce service controller code in an intuitive way, not to be a be-everything DSL or limit what you can do in a controller. Choose what features to expose, and you can still define/redefine actions etc. at will.
|
10
11
|
|
11
12
|
We test with travis-ci with with Rails 3.1, 3.2, and Rails 4. Feel free to submit issues and/or do a pull requests if you run into anything.
|
12
13
|
|
13
14
|
You can use any of these for the JSON response (the view):
|
14
|
-
* [
|
15
|
-
* JBuilder
|
16
|
-
*
|
15
|
+
* [ActiveModel::Serializers][active_model_serializers]
|
16
|
+
* [JBuilder][jbuilder]
|
17
|
+
* Almost anything else that will work with render/respond_with without anything special in the controller action method implementation.
|
17
18
|
|
18
19
|
And can use any of the following for authorizing parameters in the incoming JSON (for create/update):
|
19
|
-
*
|
20
|
-
* [Strong Parameters][strong_parameters]
|
20
|
+
* [Permitters][permitters]
|
21
|
+
* [Strong Parameters][strong_parameters]
|
21
22
|
* Mass assignment security in Rails 3.x (attr_accessible, etc.).
|
22
23
|
|
23
24
|
An example app using an older version of restful_json with AngularJS is [employee-training-tracker][employee-training-tracker], featured in [Built with AngularJS][built_with_angularjs].
|
24
25
|
|
25
|
-
###
|
26
|
-
|
27
|
-
In your Rails app's `Gemfile`:
|
26
|
+
### Usage
|
28
27
|
|
29
|
-
|
28
|
+
You have a configurable generic Rails 3.1.x/3.2.x/4.0.x controller that does the index, show, create, and update and other custom actions easily for you.
|
30
29
|
|
31
|
-
|
30
|
+
Everything is well-declared and fairly concise.
|
32
31
|
|
33
|
-
|
34
|
-
gem 'strong_parameters', '~> 0.2.0'
|
35
|
-
# comment this out if you don't plan to use Permitters
|
36
|
-
gem 'cancan', '~> 1.6.9'
|
37
|
-
# comment this out if you don't plan to use ActiveModel::Serializers
|
38
|
-
gem 'active_model_serializers', '~> 0.7.0'
|
39
|
-
|
40
|
-
Then:
|
32
|
+
You can have something as simple as:
|
41
33
|
|
42
|
-
|
34
|
+
```ruby
|
35
|
+
class FoobarsController < ApplicationController
|
36
|
+
include RestfulJson::DefaultController
|
37
|
+
end
|
38
|
+
```
|
43
39
|
|
44
|
-
|
40
|
+
which would use the restful_json configuration and the controller's classname for the service definition and provide a simple no-frills JSON CRUD controller that behaves somewhat similarly to a Rails controller created via `rails g scaffold ...`.
|
45
41
|
|
46
|
-
|
42
|
+
Or, you can define many more bells and whistles with declarative ARel through HTTP(S):
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class FoobarsController < ApplicationController
|
46
|
+
include RestfulJson::DefaultController
|
47
|
+
|
48
|
+
query_for :index, is: ->(t,q) {q.joins(:apples, :pears).where(apples: {color: 'green'}).where(pears: {color: 'green'})}
|
49
|
+
|
50
|
+
# use can_filter_by followed by the request parameter name(s)
|
51
|
+
|
52
|
+
# implies using: [:eq] because RestfulJson.can_filter_by_default_using = [:eq]
|
53
|
+
can_filter_by :foo_id
|
54
|
+
|
55
|
+
can_filter_by :foo_date, :bar_date, using: [:lt, :eq, :gt], with_default: Time.now
|
56
|
+
|
57
|
+
can_filter_by :a_request_param_name, with_query: ->(t,q,param_value) {q.joins(:some_assoc).where(:some_assocs_table_name=>{some_attr: param_value})}
|
58
|
+
|
59
|
+
can_filter_by :and_another, through: [:some_attribute_on_this_model]
|
60
|
+
|
61
|
+
can_filter_by :one_more, through: [:some_association, :some_attribute_on_some_association_model]
|
62
|
+
|
63
|
+
can_filter_by :and_one_more, through: [:my_assoc, :my_assocs_assoc, :my_assocs_assocs_assoc, :an_attribute_on_my_assocs_assocs_assoc]
|
64
|
+
|
65
|
+
supports_functions :count, :uniq, :take, :skip, :page, :page_count
|
66
|
+
|
67
|
+
order_by {:foo_date => :asc}, :foo_color, {:bar_date => :desc} # assumes :asc for foo_color, since hash not provided
|
68
|
+
|
69
|
+
serialize_action :index, with: ListFoobarSerializer
|
70
|
+
|
71
|
+
# optional. by default acts like Rails and will serve in json or html
|
72
|
+
respond_to :json, :html
|
73
|
+
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
Now you can query like these:
|
78
|
+
|
79
|
+
```
|
80
|
+
https://example.org/foobars?foo_id=123
|
81
|
+
https://example.org/foobars?bar_date!gt=2012-08-08
|
82
|
+
https://example.org/foobars?bar_date!gt=2012-08-08&count=
|
83
|
+
https://example.org/foobars?and_one_more=an_attribute_value_on_my_assocs_assocs_assoc&uniq=
|
84
|
+
https://example.org/foobars?page_count=
|
85
|
+
https://example.org/foobars?page=1
|
86
|
+
https://example.org/foobars?skip=30&take=15
|
87
|
+
```
|
47
88
|
|
48
|
-
|
89
|
+
### Installation
|
49
90
|
|
50
|
-
|
91
|
+
In your Rails app's `Gemfile`:
|
51
92
|
|
52
|
-
|
93
|
+
```ruby
|
94
|
+
gem 'restful_json', '~> 4.0.0'
|
95
|
+
```
|
53
96
|
|
54
|
-
|
97
|
+
Then:
|
55
98
|
|
56
|
-
|
99
|
+
```
|
100
|
+
bundle install
|
101
|
+
```
|
57
102
|
|
58
|
-
|
103
|
+
#### Optional
|
59
104
|
|
60
|
-
|
105
|
+
##### Strong Parameters
|
61
106
|
|
62
|
-
|
107
|
+
[Strong Parameters][strong_parameters] is part of Rails 4, so *don't* include this gem if using Rails 4. However, if you are using Rails 3, it can be used on its own or as a dependency of Permitters:
|
63
108
|
|
64
|
-
|
109
|
+
```ruby
|
110
|
+
gem 'strong_parameters', '~> 0.2.0'
|
111
|
+
```
|
65
112
|
|
66
|
-
|
113
|
+
Be sure to read the [Strong Parameters][strong_parameters] docs, because you need to use `config.active_record.whitelist_attributes = false` in your app config, etc. if using Rails 3. Also, this removes the need for `attr_accessible` or `attr_protected` in your models, so convert those restrictions to either Permitters or Strong Parameters. And you'll need `ActiveModel::ForbiddenAttributesProtection` included in your models.
|
67
114
|
|
68
|
-
|
115
|
+
As noted in [Strong Parameters][strong_parameters], it is suggested to encapsulate the permitting into a private method in the controller, so we allow:
|
69
116
|
|
70
|
-
|
117
|
+
```ruby
|
118
|
+
def foobar_params
|
119
|
+
params.require(:foobar).permit(:name, :age)
|
120
|
+
end
|
121
|
+
```
|
71
122
|
|
72
|
-
|
123
|
+
or if `self.allow_action_specific_params_methods = true` is set in restful_json configuration, as it is by default:
|
73
124
|
|
74
|
-
|
125
|
+
```ruby
|
126
|
+
def create_foobar_params
|
127
|
+
params.require(:foobar).permit(:name, :age)
|
128
|
+
end
|
75
129
|
|
76
|
-
|
77
|
-
|
130
|
+
def update_foobar_params
|
131
|
+
params.require(:foobar).permit(:age)
|
132
|
+
end
|
133
|
+
```
|
78
134
|
|
79
|
-
|
80
|
-
User.new
|
81
|
-
end
|
82
|
-
end
|
135
|
+
and even other actions if you want:
|
83
136
|
|
84
|
-
|
137
|
+
```ruby
|
138
|
+
def index_foobars_params
|
139
|
+
params.require(:foobars).permit(:foo_id)
|
140
|
+
end
|
85
141
|
|
86
|
-
|
142
|
+
# where 'some_action' is a custom action created by query_for
|
143
|
+
def some_action_foobars_params
|
144
|
+
params.require(:foobars).permit(:foo_id)
|
145
|
+
end
|
87
146
|
|
88
|
-
|
89
|
-
|
147
|
+
def show_foobar_params
|
148
|
+
params.require(:foobar).permit(:id)
|
149
|
+
end
|
150
|
+
```
|
90
151
|
|
91
|
-
|
92
|
-
can :manage, :all
|
93
|
-
end
|
94
|
-
end
|
152
|
+
##### Permitters
|
95
153
|
|
96
|
-
|
154
|
+
[Permitters][permitters] can use Strong Parameters and CanCan or another authorization solution for parameter authorization:
|
97
155
|
|
98
|
-
|
156
|
+
```ruby
|
157
|
+
gem 'permitters', '~> 0.0.1'
|
158
|
+
```
|
99
159
|
|
100
|
-
|
160
|
+
The default restful_json configuration is for only create and update actions to use permitters:
|
101
161
|
|
102
|
-
|
162
|
+
```ruby
|
163
|
+
self.actions_that_permit = [:create, :update]
|
164
|
+
```
|
103
165
|
|
104
|
-
|
166
|
+
Read the [Permitters][permitters] documentation for more info on how you can encapsulate and easily share permittance and authorization.
|
105
167
|
|
106
|
-
|
168
|
+
##### CanCan
|
107
169
|
|
108
|
-
|
170
|
+
[CanCan][cancan] can be used via Permitters or on its own:
|
109
171
|
|
110
|
-
|
172
|
+
```ruby
|
173
|
+
gem 'cancan', '~> 1.6.9'
|
174
|
+
```
|
111
175
|
|
112
|
-
|
176
|
+
And [CanCan][cancan] supports [Authlogic][authlogic], [Devise][devise], etc. for authentication. See the [CanCan][cancan] docs for more info.
|
113
177
|
|
114
|
-
|
178
|
+
The default restful_json configuration is for `authorize!(permission, model)` to be called for create and update:
|
115
179
|
|
116
|
-
|
180
|
+
```ruby
|
181
|
+
self.actions_that_authorize = [:create, :update]
|
182
|
+
```
|
117
183
|
|
118
|
-
|
184
|
+
So, for example, when a create is attempted, it will first call `authorize!(:create, Foobar)`.
|
119
185
|
|
120
|
-
|
186
|
+
`CanCan::ModelAdditions` needs to be included on any model that you plan to use CanCan with, per the CanCan documentation.
|
121
187
|
|
122
|
-
|
188
|
+
##### JBuilder
|
123
189
|
|
124
|
-
|
190
|
+
[JBuilder][jbuilder] comes with Rails 4, or can be included in Rails 3 to provide JSON views. See [Railscast #320][railscast320] for more info on using JBuilder:
|
125
191
|
|
126
|
-
|
192
|
+
```ruby
|
193
|
+
gem 'jbuilder', '~> 1.3.0'
|
194
|
+
```
|
127
195
|
|
128
|
-
|
196
|
+
If you want to enable JBuilder for all restful_json services, you may want to disable all renders and respond_withs in the controller:
|
129
197
|
|
130
|
-
|
198
|
+
```ruby
|
199
|
+
RestfulJson.render_enabled = false
|
200
|
+
```
|
131
201
|
|
132
|
-
|
202
|
+
##### ActiveModel::Serializers
|
133
203
|
|
134
|
-
|
204
|
+
[ActiveModel::Serializers][active_model_serializers] is great for specifying what should go into the JSON responses as an alternative to JBuilder, and restful_json provides a `serialize_action` method to specify custom serializer if you don't want to use the default, e.g. `serialize_action :index, with: BarsSerializer` and `serialize_action :index, :my_other_list_action, with: BarsSerializer`.
|
135
205
|
|
136
|
-
|
206
|
+
```ruby
|
207
|
+
gem 'active_model_serializers', '~> 0.7.0'
|
208
|
+
```
|
137
209
|
|
138
|
-
|
210
|
+
Because of some issues with some versions of ActiveModel::Serializers using respond_with, you might want to set the option:
|
139
211
|
|
140
|
-
|
212
|
+
```ruby
|
213
|
+
RestfulJson.avoid_respond_with = true
|
214
|
+
```
|
141
215
|
|
142
|
-
|
216
|
+
##### Mass Assignment Security
|
143
217
|
|
144
|
-
|
218
|
+
To use mass assignment security in Rails 3.x, specify this in restful_json config/controller config:
|
145
219
|
|
146
|
-
|
220
|
+
```ruby
|
221
|
+
self.use_permitters = false
|
222
|
+
```
|
147
223
|
|
148
|
-
|
224
|
+
Don't use any of these, as they each include Strong Parameters:
|
149
225
|
|
150
|
-
|
226
|
+
```ruby
|
227
|
+
include ActionController::StrongParameters
|
228
|
+
include RestfulJson::DefaultController
|
229
|
+
```
|
151
230
|
|
152
|
-
|
231
|
+
Only the main controller is needed:
|
153
232
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
/app/views/plural_name_of_model/update.json.jbuilder
|
233
|
+
```ruby
|
234
|
+
include RestfulJson::Controller
|
235
|
+
```
|
158
236
|
|
159
|
-
|
237
|
+
Then, make sure that attr_accessible and/or attr_protected, etc. are used properly.
|
160
238
|
|
161
239
|
##### Other Options
|
162
240
|
|
163
241
|
You should be able to use anything that works with normal render/responds_with in Rails controllers without additional code in the controller. If you'd like to use something that requires additional code in the action methods of the controller, and you think it would be a good fit, feel free to do a pull request.
|
164
242
|
|
165
|
-
|
166
|
-
|
167
|
-
##### Permitters
|
168
|
-
|
169
|
-
We include ApplicationPermitter and optional controller support for Adam Hawkins' [permitters][permitter].
|
170
|
-
|
171
|
-
The default setting is for permitters to be used:
|
172
|
-
|
173
|
-
self.use_permitters = true
|
243
|
+
### Application Configuration
|
174
244
|
|
175
|
-
|
245
|
+
At the bottom of `config/environment.rb`, you can set restful_json can be configured one line at a time.
|
176
246
|
|
177
|
-
|
247
|
+
```ruby
|
248
|
+
RestfulJson.debug = true
|
249
|
+
```
|
178
250
|
|
179
|
-
|
180
|
-
# attributes we accept (the new way to do attr_accessible, OO-styley! Thanks, twinturbo)
|
181
|
-
permit :id, :foo_id
|
182
|
-
permit :bar_id
|
183
|
-
permit :notes
|
184
|
-
# foobar has accepts_nested_attributes_for :barfoos
|
185
|
-
scope :barfoos_attributes do |barfoo|
|
186
|
-
barfoo.permit :id, :favorite_color, :favorite_chicken
|
187
|
-
end
|
188
|
-
end
|
251
|
+
or in bulk, like:
|
189
252
|
|
190
|
-
|
253
|
+
```ruby
|
254
|
+
RestfulJson.configure do
|
191
255
|
|
192
|
-
|
193
|
-
|
256
|
+
# default for :using in can_filter_by
|
257
|
+
self.can_filter_by_default_using = [:eq]
|
194
258
|
|
195
|
-
|
259
|
+
# to log debug info during request handling
|
260
|
+
self.debug = false
|
196
261
|
|
197
|
-
|
262
|
+
# delimiter for values in request parameter values
|
263
|
+
self.filter_split = ','.freeze
|
198
264
|
|
199
|
-
|
265
|
+
# equivalent to specifying respond_to :json, :html in the controller, and can be overriden in the controller. Note that by default responders gem sets respond_to :html in application_controller.rb.
|
266
|
+
self.formats = :json, :html
|
200
267
|
|
201
|
-
|
268
|
+
# default number of records to return if using the page request function
|
269
|
+
self.number_of_records_in_a_page = 15
|
202
270
|
|
203
|
-
|
204
|
-
|
205
|
-
end
|
271
|
+
# delimiter for ARel predicate in the request parameter name
|
272
|
+
self.predicate_prefix = '!'.freeze
|
206
273
|
|
207
|
-
|
274
|
+
# if true, will render resource and HTTP 201 for post/create or resource and HTTP 200 for put/update. ignored if render_enabled is false.
|
275
|
+
self.return_resource = false
|
208
276
|
|
209
|
-
|
277
|
+
# if false, controller actions will just set instance variable and return it instead of calling setting instance variable and then calling render/respond_with
|
278
|
+
self.render_enabled = true
|
210
279
|
|
211
|
-
|
280
|
+
# use Permitters
|
281
|
+
self.use_permitters = true
|
212
282
|
|
213
|
-
|
283
|
+
# instead of using Rails default respond_with, explicitly define render in respond_with block
|
284
|
+
self.avoid_respond_with = true
|
214
285
|
|
215
|
-
|
216
|
-
|
217
|
-
acts_as_restful_json
|
286
|
+
# use the permitter_class for create and update, if use_permitters = true
|
287
|
+
self.action_to_permitter = {create: nil, update: nil}
|
218
288
|
|
219
|
-
|
289
|
+
# the methods that call authorize! action_sym, @model_class
|
290
|
+
self.actions_that_authorize = [:create, :update]
|
291
|
+
|
292
|
+
# if not using permitters, will check respond_to?("(action)_(plural_or_singular_model_name)_params".to_sym) and if true will __send__(method)
|
293
|
+
self.allow_action_specific_params_methods = true
|
294
|
+
|
295
|
+
# if not using permitters, will check respond_to?("(singular_model_name)_params".to_sym) and if true will __send__(method)
|
296
|
+
self.actions_that_permit = [:create, :update]
|
220
297
|
|
221
|
-
|
298
|
+
# in error JSON, break out the exception info into fields for debugging
|
299
|
+
self.return_error_data = true
|
222
300
|
|
223
|
-
|
301
|
+
# the class that is rescued in each action method, but if nil will always reraise and not handle
|
302
|
+
self.rescue_class = StandardError
|
303
|
+
|
304
|
+
# will define order of errors handled and what status and/or i18n message key to use
|
305
|
+
self.rescue_handlers = []
|
224
306
|
|
225
|
-
|
307
|
+
# rescue_handlers are an ordered array of handlers to handle rescue of self.rescue_class or sub types.
|
308
|
+
# can use optional i18n_key for message, but will default to e.message if i18n_key not found.
|
226
309
|
|
227
|
-
|
310
|
+
# support 404 error for ActiveRecord::RecordNotFound if using ActiveRecord.
|
311
|
+
begin
|
312
|
+
require 'active_record/errors'
|
313
|
+
self.rescue_handlers << {exception_classes: [ActiveRecord::RecordNotFound], status: :not_found, i18n_key: 'api.not_found'.freeze}
|
314
|
+
rescue LoadError, NameError
|
315
|
+
end
|
228
316
|
|
229
|
-
|
317
|
+
# support 403 error for CanCan::AccessDenied if using CanCan
|
318
|
+
begin
|
319
|
+
require 'cancan/exceptions'
|
320
|
+
self.rescue_handlers << {exception_classes: [CanCan::AccessDenied], status: :forbidden, i18n_key: 'api.not_found'.freeze}
|
321
|
+
rescue LoadError, NameError
|
322
|
+
end
|
230
323
|
|
231
|
-
|
324
|
+
# support 500 error for everything else that is a self.rescue_class (in action)
|
325
|
+
self.rescue_handlers << {status: :internal_server_error, i18n_key: 'api.internal_server_error'.freeze}
|
232
326
|
|
233
|
-
|
234
|
-
|
235
|
-
# default for :using in can_filter_by
|
236
|
-
self.can_filter_by_default_using = [:eq]
|
237
|
-
|
238
|
-
# to output debugging info during request handling
|
239
|
-
self.debug = false
|
240
|
-
|
241
|
-
# delimiter for values in request parameter values
|
242
|
-
self.filter_split = ','
|
243
|
-
|
244
|
-
# equivalent to specifying respond_to :json, :html in the controller, and can be overriden in the controller. Note that by default responders gem sets respond_to :html in application_controller.rb.
|
245
|
-
self.formats = :json, :html
|
246
|
-
|
247
|
-
# default number of records to return if using the page request function
|
248
|
-
self.number_of_records_in_a_page = 15
|
249
|
-
|
250
|
-
# delimiter for ARel predicate in the request parameter name
|
251
|
-
self.predicate_prefix = '!'
|
252
|
-
|
253
|
-
# if true, will render resource and HTTP 201 for post/create or resource and HTTP 200 for put/update. ignored if render_enabled is false.
|
254
|
-
self.return_resource = false
|
255
|
-
|
256
|
-
# if false, controller actions will just set instance variable and return it instead of calling setting instance variable and then calling render/respond_with
|
257
|
-
self.render_enabled = true
|
258
|
-
|
259
|
-
# if false, will assume that it should either try calling create_(single model name)_params or fall back to calling (single model name)_params if create, or update_(single model name)_params then (single model name)_params if that didn't respond, if update. if it can't call those, it will either use mass assignment security, no parameter security, or some other solution, depending on how it is configured.
|
260
|
-
self.use_permitters = true
|
261
|
-
|
262
|
-
end
|
327
|
+
end
|
328
|
+
```
|
263
329
|
|
264
330
|
### Controller Configuration
|
265
331
|
|
@@ -267,158 +333,163 @@ In the controller, you can set a variety of class attributes with `self.somethin
|
|
267
333
|
|
268
334
|
All of the app-level configuration parameters are configurable at the controller level:
|
269
335
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
336
|
+
```ruby
|
337
|
+
self.can_filter_by_default_using = [:eq]
|
338
|
+
self.debug = false
|
339
|
+
self.filter_split = ','.freeze
|
340
|
+
self.formats = :json, :html
|
341
|
+
self.number_of_records_in_a_page = 15
|
342
|
+
self.predicate_prefix = '!'.freeze
|
343
|
+
self.return_resource = false
|
344
|
+
self.render_enabled = true
|
345
|
+
self.use_permitters = true
|
346
|
+
self.avoid_respond_with = true
|
347
|
+
self.return_error_data = true
|
348
|
+
self.rescue_class = StandardError
|
349
|
+
self.action_to_permitter = {create: nil, update: nil}
|
350
|
+
self.actions_that_authorize = [:create, :update]
|
351
|
+
self.allow_action_specific_params_methods = true
|
352
|
+
self.actions_that_permit = [:create, :update]
|
353
|
+
|
354
|
+
require 'active_record/errors'
|
355
|
+
require 'cancan/exceptions'
|
356
|
+
self.rescue_handlers [
|
357
|
+
{exception_classes: [ActiveRecord::RecordNotFound], status: :not_found, i18n_key: 'api.not_found'.freeze},
|
358
|
+
{exception_classes: [CanCan::AccessDenied], status: :forbidden, i18n_key: 'api.not_found'.freeze},
|
359
|
+
{status: :internal_server_error, i18n_key: 'api.internal_server_error'.freeze}
|
360
|
+
]
|
361
|
+
```
|
279
362
|
|
280
363
|
In addition there are some that are controller-only...
|
281
364
|
|
282
365
|
If you don't use the standard controller naming convention, you can define this in the controller:
|
283
366
|
|
284
|
-
|
367
|
+
```ruby
|
368
|
+
self.model_class = YourModel
|
369
|
+
```
|
285
370
|
|
286
371
|
If it doesn't handle the other forms well, you can explicitly define the singular/plural names:
|
287
372
|
|
288
|
-
|
289
|
-
|
373
|
+
```ruby
|
374
|
+
self.model_singular_name = 'your_model'
|
375
|
+
self.model_plural_name = 'your_models'
|
376
|
+
```
|
290
377
|
|
291
378
|
These are used for *_url method definitions, to set instance variables like `@foobar` and `@foobars` dynamically, etc.
|
292
379
|
|
293
380
|
Other class attributes are available for setting/overriding, but they are all set by the other class methods defined in the next section.
|
294
381
|
|
295
|
-
### Usage
|
296
|
-
|
297
|
-
You have a configurable generic Rails 3.1.x/3.2.x/4.0.x controller that does the index, show, create, and update and other custom actions easily for you.
|
298
|
-
|
299
|
-
Everything is well-declared and fairly concise.
|
300
|
-
|
301
|
-
You can have something as simple as:
|
302
|
-
|
303
|
-
class FoobarsController < ApplicationController
|
304
|
-
include RestfulJson::DefaultController
|
305
|
-
end
|
306
|
-
|
307
|
-
which would use the restful_json configuration and the controller's classname for the service definition and provide a simple no-frills JSON CRUD controller that behaves somewhat similarly to a Rails controller created via `rails g scaffold ...`.
|
308
|
-
|
309
|
-
Or, you can define many more bells and whistles:
|
310
|
-
|
311
|
-
class FoobarsController < ApplicationController
|
312
|
-
include RestfulJson::DefaultController
|
313
|
-
|
314
|
-
query_for :index, is: ->(t,q) {q.joins(:apples, :pears).where(apples: {color: 'green'}).where(pears: {color: 'green'})}
|
315
|
-
|
316
|
-
# args sent to can_filter_by are the request parameter name(s)
|
317
|
-
|
318
|
-
# implies using: [:eq] because RestfulJson.can_filter_by_default_using = [:eq]
|
319
|
-
can_filter_by :foo_id
|
320
|
-
|
321
|
-
# can specify multiple predicates and optionally a default value
|
322
|
-
can_filter_by :foo_date, :bar_date, using: [:lt, :eq, :gt], with_default: Time.now
|
323
|
-
|
324
|
-
can_filter_by :a_request_param_name, with_query: ->(t,q,param_value) {q.joins(:some_assoc).where(:some_assocs_table_name=>{some_attr: param_value})}
|
325
|
-
|
326
|
-
can_filter_by :and_another, through: [:some_attribute_on_this_model]
|
327
|
-
|
328
|
-
can_filter_by :one_more, through: [:some_association, :some_attribute_on_some_association_model]
|
329
|
-
|
330
|
-
can_filter_by :and_one_more, through: [:my_assoc, :my_assocs_assoc, :my_assocs_assocs_assoc, :an_attribute_on_my_assocs_assocs_assoc]
|
331
|
-
|
332
|
-
supports_functions :count, :uniq, :take, :skip, :page, :page_count
|
333
|
-
|
334
|
-
order_by {:foo_date => :asc}, :foo_color, {:bar_date => :desc} # an ordered array of hashes, assumes :asc if not a hash
|
335
|
-
|
336
|
-
serialize_action :index, with: ListFoobarSerializer
|
337
|
-
|
338
|
-
# comma-delimited if you want more than :json, e.g. :json, :html
|
339
|
-
respond_to :json, :html
|
340
|
-
|
341
|
-
end
|
342
|
-
|
343
382
|
#### Routing
|
344
383
|
|
345
384
|
You can just add normal Rails RESTful routes in `config/routes.rb`, e.g. for the Foobar model:
|
346
385
|
|
347
|
-
|
348
|
-
|
349
|
-
|
386
|
+
```ruby
|
387
|
+
MyAppName::Application.routes.draw do
|
388
|
+
resources :foobars
|
389
|
+
end
|
390
|
+
```
|
350
391
|
|
351
392
|
Supports static, nested, etc. routes also, e.g.:
|
352
393
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
394
|
+
```ruby
|
395
|
+
MyAppName::Application.routes.draw do
|
396
|
+
namespace :my_service_controller_module do
|
397
|
+
resources :foobars
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
358
401
|
|
359
402
|
Can pass in params from the path for use in filters, etc. as if they were request parameters:
|
360
403
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
404
|
+
```ruby
|
405
|
+
MyAppName::Application.routes.draw do
|
406
|
+
namespace :my_service_controller_module do
|
407
|
+
match 'bar/:bar_id/foobars(.:format)' => 'foobars#index'
|
408
|
+
end
|
409
|
+
end
|
410
|
+
```
|
366
411
|
|
367
412
|
#### Default Filtering by Attribute(s)
|
368
413
|
|
369
414
|
First, declare in the controller:
|
370
415
|
|
371
|
-
|
416
|
+
```ruby
|
417
|
+
can_filter_by :foo_id
|
418
|
+
```
|
372
419
|
|
373
420
|
If `RestfulJson.can_filter_by_default_using = [:eq]` as it is by default, then you can now get Foobars with a foo_id of '1':
|
374
421
|
|
375
|
-
|
422
|
+
```
|
423
|
+
http://localhost:3000/foobars?foo_id=1
|
424
|
+
```
|
376
425
|
|
377
426
|
`can_filter_by` without an option means you can send in that request param (via routing or directly, just like normal in Rails) and it will use that in the ARel query (safe from SQL injection and only letting you do what you tell it). `:using` means you can use those [ARel][arel] predicates for filtering. If you do `Arel::Predications.public_instance_methods.sort` in Rails console, you can see a list of the available predicates. So, you could get crazy with:
|
378
427
|
|
379
|
-
|
428
|
+
```ruby
|
429
|
+
can_filter_by :does_not_match, :does_not_match_all, :does_not_match_any, :eq, :eq_all, :eq_any, :gt, :gt_all, :gt_any, :gteq, :gteq_all, :gteq_any, :in, :in_all, :in_any, :lt, :lt_all, :lt_any, :lteq, :lteq_all, :lteq_any, :matches, :matches_all, :matches_any, :not_eq, :not_eq_all, :not_eq_any, :not_in, :not_in_all, :not_in_any
|
430
|
+
```
|
380
431
|
|
381
432
|
`can_filter_by` can also specify a `:with_query` to provide a lambda that takes the request parameter in when it is provided by the request.
|
382
433
|
|
383
|
-
|
434
|
+
```ruby
|
435
|
+
can_filter_by :a_request_param_name, with_query: ->(t,q,param_value) {q.joins(:some_assoc).where(:some_assocs_table_name=>{some_attr: param_value})}
|
436
|
+
```
|
384
437
|
|
385
438
|
And `can_filter_by` can specify a `:through` to provide an easy way to inner join through a bunch of models using ActiveRecord relations, by specifying 0-to-many association names to go "through" to the final argument, which is the attribute name on the last model. The following is equivalent to the last query:
|
386
439
|
|
387
|
-
|
440
|
+
```ruby
|
441
|
+
can_filter_by :a_request_param_name, through: [:some_assoc, :some_attr]
|
442
|
+
```
|
388
443
|
|
389
444
|
Let's say you are in MagicalValleyController, and the MagicalValley model `has many :magical_unicorns`. The MagicalUnicorn model has an attribute called `name`. You want to return MagicalValleys that are associated with all of the MagicalUnicorns named 'Rainbow'. You could do either:
|
390
445
|
|
391
|
-
|
446
|
+
```ruby
|
447
|
+
can_filter_by :magical_unicorn_name, with_query: ->(t,q,param_value) {q.joins(:magical_unicorns).where(:magical_unicorns=>{name: param_value})}
|
448
|
+
```
|
392
449
|
|
393
450
|
or:
|
394
451
|
|
395
|
-
|
452
|
+
```ruby
|
453
|
+
can_filter_by :magical_unicorn_name, through: [:magical_unicorns, :name]
|
454
|
+
```
|
396
455
|
|
397
456
|
and you can then use this:
|
398
457
|
|
399
|
-
|
458
|
+
```
|
459
|
+
http://localhost:3000/magical_valleys?magical_unicorn_name=Rainbow
|
460
|
+
```
|
400
461
|
|
401
462
|
or if a MagicalUnicorn `has_many :friends` and a MagicalUnicorn's friend has a name attribute:
|
402
463
|
|
403
|
-
|
464
|
+
```ruby
|
465
|
+
can_filter_by :magical_unicorn_friend_name, through: [:magical_unicorns, :friends, :name]
|
466
|
+
```
|
404
467
|
|
405
468
|
and use this to get valleys associated with unicorns who in turn have a friend named Oscar:
|
406
469
|
|
407
|
-
|
470
|
+
```
|
471
|
+
http://localhost:3000/magical_valleys?magical_unicorn_friend_name=Oscar
|
472
|
+
```
|
408
473
|
|
409
474
|
#### Other Filters by Attribute(s)
|
410
475
|
|
411
476
|
First, declare in the controller:
|
412
477
|
|
413
|
-
|
478
|
+
```ruby
|
479
|
+
can_filter_by :seen_on, using: [:gteq, :eq_any]
|
480
|
+
```
|
414
481
|
|
415
482
|
Get Foobars with seen_on of 2012-08-08 or later using the [ARel][arel] gteq predicate splitting the request param on `predicate_prefix` (configurable), you'd use:
|
416
483
|
|
417
|
-
|
484
|
+
```
|
485
|
+
http://localhost:3000/foobars?seen_on!gteq=2012-08-08
|
486
|
+
```
|
418
487
|
|
419
488
|
Multiple values are separated by `filter_split` (configurable):
|
420
489
|
|
421
|
-
|
490
|
+
```
|
491
|
+
http://localhost:3000/foobars?seen_on!eq_any=2012-08-08,2012-09-09
|
492
|
+
```
|
422
493
|
|
423
494
|
#### Supported Functions
|
424
495
|
|
@@ -430,185 +501,266 @@ Multiple values are separated by `filter_split` (configurable):
|
|
430
501
|
|
431
502
|
First, declare in the controller:
|
432
503
|
|
433
|
-
|
504
|
+
```ruby
|
505
|
+
supports_functions :uniq
|
506
|
+
```
|
434
507
|
|
435
508
|
Now this works:
|
436
509
|
|
437
|
-
|
510
|
+
```
|
511
|
+
http://localhost:3000/foobars?uniq=
|
512
|
+
```
|
438
513
|
|
439
514
|
##### Count
|
440
515
|
|
441
516
|
First, declare in the controller:
|
442
517
|
|
443
|
-
|
518
|
+
```ruby
|
519
|
+
supports_functions :count
|
520
|
+
```
|
444
521
|
|
445
522
|
Now this works:
|
446
523
|
|
447
|
-
|
524
|
+
```
|
525
|
+
http://localhost:3000/foobars?count=
|
526
|
+
```
|
448
527
|
|
449
528
|
##### Paging
|
450
529
|
|
451
530
|
First, declare in the controller:
|
452
531
|
|
453
|
-
|
532
|
+
```ruby
|
533
|
+
supports_functions :page, :page_count
|
534
|
+
```
|
454
535
|
|
455
536
|
Now you can get the page count:
|
456
537
|
|
457
|
-
|
538
|
+
```
|
539
|
+
http://localhost:3000/foobars?page_count=
|
540
|
+
```
|
458
541
|
|
459
542
|
And access each page of results:
|
460
543
|
|
461
|
-
|
462
|
-
|
463
|
-
|
544
|
+
```
|
545
|
+
http://localhost:3000/foobars?page=1
|
546
|
+
http://localhost:3000/foobars?page=2
|
547
|
+
```
|
464
548
|
|
465
549
|
To set page size at application level:
|
466
550
|
|
467
|
-
|
551
|
+
```ruby
|
552
|
+
RestfulJson.number_of_records_in_a_page = 15
|
553
|
+
```
|
468
554
|
|
469
555
|
To set page size at controller level:
|
470
556
|
|
471
|
-
|
557
|
+
```ruby
|
558
|
+
self.number_of_records_in_a_page = 15
|
559
|
+
```
|
472
560
|
|
473
561
|
##### Skip and Take (OFFSET and LIMIT)
|
474
562
|
|
475
563
|
First, declare in the controller:
|
476
564
|
|
477
|
-
|
565
|
+
```ruby
|
566
|
+
supports_functions :skip, :take
|
567
|
+
```
|
478
568
|
|
479
569
|
To skip rows returned, use 'skip'. It is called take, because skip is the [ARel][arel] equivalent of SQL OFFSET:
|
480
570
|
|
481
|
-
|
571
|
+
```
|
572
|
+
http://localhost:3000/foobars?skip=5
|
573
|
+
```
|
482
574
|
|
483
575
|
To limit the number of rows returned, use 'take'. It is called take, because take is the [ARel][arel] equivalent of SQL LIMIT:
|
484
576
|
|
485
|
-
|
577
|
+
```
|
578
|
+
http://localhost:3000/foobars.json?take=5
|
579
|
+
```
|
486
580
|
|
487
581
|
Combine skip and take for manual completely customized paging, e.g.
|
488
582
|
|
489
|
-
|
490
|
-
|
491
|
-
|
583
|
+
```
|
584
|
+
http://localhost:3000/foobars?take=15
|
585
|
+
http://localhost:3000/foobars?skip=15&take=15
|
586
|
+
http://localhost:3000/foobars?skip=30&take=15
|
587
|
+
```
|
588
|
+
|
589
|
+
##### Custom Queries
|
590
|
+
|
591
|
+
To filter the list where the status_code attribute is 'green':
|
592
|
+
|
593
|
+
```ruby
|
594
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped
|
595
|
+
query_for :index, is: lambda {|t,q| q.where(:status_code => 'green')}
|
596
|
+
```
|
597
|
+
|
598
|
+
or use the `->` Ruby 1.9 lambda stab operator (note lack of whitespace between stab and parenthesis):
|
599
|
+
|
600
|
+
```ruby
|
601
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped
|
602
|
+
query_for :index, is: is: ->(t,q) {q.where(:status_code => 'green')}
|
603
|
+
```
|
604
|
+
|
605
|
+
You can also filter out items that have associations that don't have a certain attribute value (or anything else you can think up with [ARel][arel]/[ActiveRecord relations][ar]), e.g. to filter the list where the object's apples and pears associations are green:
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped
|
609
|
+
# note: must be no space between -> and parenthesis
|
610
|
+
query_for :index, is: ->(t,q) {
|
611
|
+
q.joins(:apples, :pears)
|
612
|
+
.where(apples: {color: 'green'})
|
613
|
+
.where(pears: {color: 'green'})
|
614
|
+
}
|
615
|
+
```
|
616
|
+
|
617
|
+
##### Define Custom Actions with Custom Queries
|
618
|
+
|
619
|
+
You are still working with regular controllers here, so add or override methods if you want more!
|
620
|
+
|
621
|
+
However `query_for` will create new action methods, so you can easily create custom non-RESTful action methods:
|
622
|
+
|
623
|
+
```ruby
|
624
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped
|
625
|
+
# note: must be no space between -> and parenthesis in lambda syntax!
|
626
|
+
query_for :some_action, is: ->(t,q) {q.where(:status_code => 'green')}
|
627
|
+
```
|
628
|
+
|
629
|
+
Note that it is a proc so you can really do whatever you want with it and will have access to other things in the environment or can call another method, etc.
|
630
|
+
|
631
|
+
```ruby
|
632
|
+
query_for :some_action, is: ->(t,q) do
|
633
|
+
if @current_user.admin?
|
634
|
+
Rails.logger.debug("Notice: unfiltered results provided to admin #{@current_user.name}")
|
635
|
+
# just make sure the relation is returned!
|
636
|
+
q
|
637
|
+
else
|
638
|
+
q.where(:access => 'public')
|
639
|
+
end
|
640
|
+
end
|
641
|
+
```
|
642
|
+
|
643
|
+
Be sure to add a route for that action, e.g. in `config/routes.rb`, e.g. for the Barfoo model:
|
644
|
+
|
645
|
+
```ruby
|
646
|
+
MyAppName::Application.routes.draw do
|
647
|
+
resources :barfoos do
|
648
|
+
get 'some_action', :on => :collection
|
649
|
+
end
|
650
|
+
end
|
651
|
+
```
|
492
652
|
|
493
653
|
##### Custom Serializers
|
494
654
|
|
495
655
|
If using ActiveModel::Serializers, you can use something other than the `(singular model name)Serializer` via `serialize_action`:
|
496
656
|
|
497
|
-
|
657
|
+
```ruby
|
658
|
+
serialize_action :index, with: ListFoobarSerializer
|
659
|
+
```
|
498
660
|
|
499
661
|
The built-in actions that support custom serializers (you can add more) are: index, show, new, create, update, destroy, and any action you automatically have created via using the restful_json `query_for` method.
|
500
662
|
|
501
663
|
It will use the `serializer` option for single result actions like show, new, create, update, destroy, and the `each_serializer` option with index and custom actions. Or, you can specify `for:` with `:array` or `:each`, e.g.:
|
502
664
|
|
503
|
-
|
665
|
+
```ruby
|
666
|
+
serialize_action :index, :some_custom_action, with: FoosSerializer, for: :array
|
667
|
+
```
|
504
668
|
|
505
669
|
Or, you could just use the default serialization, if you want.
|
506
670
|
|
507
|
-
##### Custom
|
671
|
+
##### Custom Permitters
|
508
672
|
|
509
|
-
|
673
|
+
If using Permitters, you can use something other than the `(singular model name)Permitter` via `permit_action`:
|
510
674
|
|
511
|
-
|
512
|
-
|
675
|
+
```ruby
|
676
|
+
permit_action :index, with: ListFoobarPermitter
|
677
|
+
```
|
513
678
|
|
514
|
-
|
679
|
+
The built-in actions that support custom permitters (you can add more) are: index, show, new, create, update, destroy, and any action you automatically have created via using the restful_json `query_for` method.
|
515
680
|
|
516
|
-
|
517
|
-
query_for :index, is: is: ->(t,q) {q.where(:status_code => 'green')}
|
681
|
+
The default configuration specifies permitter as `nil` which indicates the default of `(singular model name)Permitter`:
|
518
682
|
|
519
|
-
|
683
|
+
```ruby
|
684
|
+
self.action_to_permitter = {create: nil, update: nil}
|
685
|
+
```
|
520
686
|
|
521
|
-
|
522
|
-
# note: must be no space between -> and parenthesis
|
523
|
-
query_for :index, is: ->(t,q) {
|
524
|
-
q.joins(:apples, :pears)
|
525
|
-
.where(apples: {color: 'green'})
|
526
|
-
.where(pears: {color: 'green'})
|
527
|
-
}
|
528
|
-
|
529
|
-
##### Define Custom Actions with Custom Queries
|
530
|
-
|
531
|
-
You are still working with regular controllers here, so add or override methods if you want more!
|
687
|
+
By using that app or controller config parameter, you can define default permitter classes for other actions that restful_json manages if you wish.
|
532
688
|
|
533
|
-
|
689
|
+
##### Strong Parameters Params Methods
|
534
690
|
|
535
|
-
|
536
|
-
# note: must be no space between -> and parenthesis in lambda syntax!
|
537
|
-
query_for :some_action, is: ->(t,q) {q.where(:status_code => 'green')}
|
691
|
+
Strong Parameters documentation suggests encapsulating permitting into a private method in the controller. We make this suggestion a convention to make development easier.
|
538
692
|
|
539
|
-
|
693
|
+
By convention, a restful_json controller can call the `(singular model name)_params` method for create and update actions. This is configured via:
|
540
694
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
# just make sure the relation is returned!
|
545
|
-
q
|
546
|
-
else
|
547
|
-
q.where(:access => 'public')
|
548
|
-
end
|
549
|
-
end
|
695
|
+
```ruby
|
696
|
+
self.actions_supporting_params_methods = [:create, :update]
|
697
|
+
```
|
550
698
|
|
551
|
-
|
699
|
+
And by default restful_json allows action specific `(action)_(model)_params` methods, so you only need to define a method like `create_foobar_params` and it will try to call that on create:
|
552
700
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
end
|
557
|
-
end
|
701
|
+
```ruby
|
702
|
+
self.allow_action_specific_params_methods = true
|
703
|
+
```
|
558
704
|
|
559
705
|
### With Rails-api
|
560
706
|
|
561
707
|
If you want to try out [rails-api][rails-api]:
|
562
708
|
|
563
|
-
|
709
|
+
```ruby
|
710
|
+
gem 'rails-api', '~> 0.0.3'
|
711
|
+
```
|
564
712
|
|
565
713
|
In `app/controllers/my_service_controller.rb`:
|
566
714
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
# If you want any additional inline class stuff, it goes here...
|
588
|
-
end
|
589
|
-
end
|
715
|
+
```ruby
|
716
|
+
module MyServiceController
|
717
|
+
extend ActiveSupport::Concern
|
718
|
+
|
719
|
+
included do
|
720
|
+
# Rails-api lets you choose features. You might not need all of these, or may need others.
|
721
|
+
include AbstractController::Translation
|
722
|
+
include ActionController::HttpAuthentication::Basic::ControllerMethods
|
723
|
+
include AbstractController::Layouts
|
724
|
+
include ActionController::MimeResponds
|
725
|
+
include ActionController::Cookies
|
726
|
+
include ActionController::ParamsWrapper
|
727
|
+
|
728
|
+
# use Permitters and AMS
|
729
|
+
include RestfulJson::DefaultController
|
730
|
+
# or comment that last line and uncomment whatever you want to use
|
731
|
+
#include ::ActionController::Serialization # AMS
|
732
|
+
#include ::ActionController::StrongParameters
|
733
|
+
#include ::TwinTurbo::Controller # Permitters which uses CanCan and Strong Parameters
|
734
|
+
#include ::RestfulJson::Controller
|
590
735
|
|
591
|
-
class
|
592
|
-
|
593
|
-
|
736
|
+
# If you want any additional inline class stuff, it goes here...
|
737
|
+
end
|
738
|
+
end
|
594
739
|
|
595
|
-
|
596
|
-
|
597
|
-
|
740
|
+
class FoobarsController < ActionController::API
|
741
|
+
include MyServiceController
|
742
|
+
end
|
743
|
+
|
744
|
+
class BarfoosController < ActionController::API
|
745
|
+
include MyServiceController
|
746
|
+
end
|
747
|
+
```
|
598
748
|
|
599
749
|
Note that in `/config/initializers/wrap_parameters.rb` you might need to add `include ActionController::ParamsWrapper` prior to the `wrap_parameters` call. For example, for unwrapped JSON, it would look like:
|
600
750
|
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
751
|
+
```ruby
|
752
|
+
ActiveSupport.on_load(:action_controller) do
|
753
|
+
# without include of ParamsWrapper, will get undefined method `wrap_parameters' for ActionController::API:Class (NoMethodError)
|
754
|
+
include ActionController::ParamsWrapper
|
755
|
+
# in this case it's expecting unwrapped params, but we could maybe use wrap_parameters format: [:json]
|
756
|
+
wrap_parameters format: []
|
757
|
+
end
|
607
758
|
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
759
|
+
# Disable root element in JSON by default.
|
760
|
+
ActiveSupport.on_load(:active_record) do
|
761
|
+
self.include_root_in_json = false
|
762
|
+
end
|
763
|
+
```
|
612
764
|
|
613
765
|
### Refactoring and Customing the Default Behavior
|
614
766
|
|
@@ -618,30 +770,34 @@ Don't subclass and include in the parent, that puts the class attributes into th
|
|
618
770
|
|
619
771
|
Don't do this:
|
620
772
|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
773
|
+
```ruby
|
774
|
+
class ServiceController < ApplicationController
|
775
|
+
include ::ActionController::Serialization
|
776
|
+
include ::ActionController::StrongParameters
|
777
|
+
include ::TwinTurbo::Controller
|
778
|
+
include ::RestfulJson::Controller
|
779
|
+
end
|
780
|
+
|
781
|
+
class FoobarsController < ServiceController
|
782
|
+
end
|
783
|
+
|
784
|
+
class BarfoosController < ServiceController
|
785
|
+
end
|
786
|
+
```
|
633
787
|
|
634
788
|
And don't do this:
|
635
789
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
790
|
+
```ruby
|
791
|
+
class FoobarsController < ApplicationController
|
792
|
+
include RestfulJson::DefaultController
|
793
|
+
end
|
794
|
+
|
795
|
+
class FoobarsController < ServiceController
|
796
|
+
end
|
797
|
+
|
798
|
+
class BarfoosController < ServiceController
|
799
|
+
end
|
800
|
+
```
|
645
801
|
|
646
802
|
It may appear to work when using the same controller or even on each new controller load, but when you make requests to BarfoosController, make a request to FoobarsController, and then make a request back to the BarfoosController, it may fail in very strange ways, such as missing column(s) from SQL results (because it isn't using the correct model).
|
647
803
|
|
@@ -649,57 +805,51 @@ It may appear to work when using the same controller or even on each new control
|
|
649
805
|
|
650
806
|
In `config/initializers/restful_json.rb` you can monkey patch the RestfulJson::Controller module. The DefaultController includes that, so it will get your changes also:
|
651
807
|
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
module ClassMethods
|
658
|
-
def hello(name)
|
659
|
-
#TODO: find way to call hook into the block call in RJ controller's included block
|
660
|
-
# without having do funny things to ActiveSupport::Concern, because append_features(base)
|
661
|
-
# defined in the monkey patch is never called, and module_eval is a royal pain.
|
662
|
-
# Or, stop using ActiveSupport::Concern. For now, we'll defined class_attribute in the
|
663
|
-
# class method that uses it and use respond_to? in a nasty hack. I'm sorry.
|
664
|
-
class_attribute :name, instance_writer: true
|
665
|
-
self.name = name
|
666
|
-
end
|
667
|
-
end
|
668
|
-
|
669
|
-
# instance methods that should be implemented or overriden.
|
670
|
-
#
|
671
|
-
# note: you don't have to do this to override service methods at the controller-level.
|
672
|
-
# Instead, just define them in the controller. this is just an example of monkey-patching.
|
673
|
-
def index
|
674
|
-
name = self.respond_to?(:name) && self.name ? self.name : 'nobody'
|
675
|
-
render :json => {:hello => self.name}
|
676
|
-
rescue => e
|
677
|
-
# rescue to identify errors that otherwise can be swallowed
|
678
|
-
puts "index failed: #{self} #{e}"
|
679
|
-
raise e
|
680
|
-
end
|
808
|
+
```ruby
|
809
|
+
module RestfulJson
|
810
|
+
module Controller
|
811
|
+
|
812
|
+
# class methods that should be implemented or overriden go in ClassMethods
|
681
813
|
|
814
|
+
module ClassMethods
|
815
|
+
def hello(name)
|
816
|
+
class_attribute :name, instance_writer: true
|
817
|
+
self.name = name
|
682
818
|
end
|
683
819
|
end
|
684
820
|
|
685
|
-
|
821
|
+
# instance methods that should be implemented or overriden.
|
686
822
|
|
687
|
-
|
688
|
-
|
689
|
-
hello 'world'
|
823
|
+
def index
|
824
|
+
render :json => {:hello => self.name}
|
690
825
|
end
|
691
826
|
|
692
|
-
|
827
|
+
end
|
828
|
+
end
|
829
|
+
```
|
830
|
+
|
831
|
+
Now in your controller, if you:
|
832
|
+
|
833
|
+
```ruby
|
834
|
+
class FoobarsController < ApplicationController
|
835
|
+
include RestfulJson::DefaultController
|
836
|
+
hello 'world'
|
837
|
+
end
|
838
|
+
```
|
693
839
|
|
694
|
-
|
840
|
+
RestfulJson::DefaultController includes RestfulJson::Controller, which you patched, so when you call:
|
695
841
|
|
696
|
-
|
842
|
+
```
|
843
|
+
http://localhost:3000/foobars
|
844
|
+
```
|
697
845
|
|
698
846
|
You would get the response:
|
699
847
|
|
700
|
-
|
848
|
+
```
|
849
|
+
{'hello': 'world'}
|
850
|
+
```
|
701
851
|
|
702
|
-
For more realistic use that takes advantage of existing configuration in the controller, take a look at the controller in `lib/restful_json/controller.rb` to see how the actions are defined, and just copy/paste into your controller or module, etc.
|
852
|
+
For more realistic use that takes advantage of existing configuration in the controller, take a look at the controller in `lib/restful_json/controller.rb` to see how the actions are defined, and just copy/paste into your controller or module, etc. and modify as needed.
|
703
853
|
|
704
854
|
### Error Handling
|
705
855
|
|
@@ -707,34 +857,36 @@ For more realistic use that takes advantage of existing configuration in the con
|
|
707
857
|
|
708
858
|
Some things restful_json can't do in the controller, like responding with json for a json request when the route is not setup correctly or an action is missing.
|
709
859
|
|
710
|
-
Rails 4 has basic error handling defined in the [public_exceptions][public_exceptions] and [show_exceptions][show_exceptions] Rack middleware.
|
860
|
+
Rails 4 has basic error handling for non-HTML formats defined in the [public_exceptions][public_exceptions] and [show_exceptions][show_exceptions] Rack middleware.
|
711
861
|
|
712
862
|
Rails 3.2.x has support for `config.exceptions_app` which can be defined as the following to simulate Rails 4 exception handling:
|
713
863
|
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
end
|
864
|
+
```ruby
|
865
|
+
config.exceptions_app = lambda do |env|
|
866
|
+
exception = env["action_dispatch.exception"]
|
867
|
+
status = env["PATH_INFO"][1..-1]
|
868
|
+
request = ActionDispatch::Request.new(env)
|
869
|
+
content_type = request.formats.first
|
870
|
+
body = { :status => status, :error => exception.message }
|
871
|
+
format = content_type && "to_#{content_type.to_sym}"
|
872
|
+
if format && body.respond_to?(format)
|
873
|
+
formatted_body = body.public_send(format)
|
874
|
+
[status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
|
875
|
+
'Content-Length' => body.bytesize.to_s}, [formatted_body]]
|
876
|
+
else
|
877
|
+
found = false
|
878
|
+
path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
|
879
|
+
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
|
880
|
+
|
881
|
+
if found || File.exist?(path)
|
882
|
+
[status, {'Content-Type' => "text/html; charset=#{ActionDispatch::Response.default_charset}",
|
883
|
+
'Content-Length' => body.bytesize.to_s}, [File.read(path)]]
|
884
|
+
else
|
885
|
+
[404, { "X-Cascade" => "pass" }, []]
|
737
886
|
end
|
887
|
+
end
|
888
|
+
end
|
889
|
+
```
|
738
890
|
|
739
891
|
That is just a collapsed version of the behavior of [public_exceptions][public_exceptions] as of April 2013, pre-Rails 4.0.0, so please look at the latest version and adjust accordingly. Use at your own risk, obviously.
|
740
892
|
|
@@ -742,7 +894,15 @@ Unfortunately, this doesn't work for Rails 3.1.x. However, in many scenarios the
|
|
742
894
|
|
743
895
|
But, if you can make Rack respond a little better for some errors, that's great.
|
744
896
|
|
745
|
-
|
897
|
+
To let all errors and exceptions fall out of restful_json action methods so that they will all be handled (without `error_data` in response) in the same way as routing, missing action, and other errors caught by Rack, just use:
|
898
|
+
|
899
|
+
```ruby
|
900
|
+
RestfulJson.configure do
|
901
|
+
self.rescue_handlers = []
|
902
|
+
end
|
903
|
+
```
|
904
|
+
|
905
|
+
#### Controller Error-handling Configuration
|
746
906
|
|
747
907
|
The default configuration will rescue StandardError in each action method and will render as 404 for ActiveRecord::RecordNotFound or 500 for all other StandardError (and ancestors, like a normal rescue).
|
748
908
|
|
@@ -752,58 +912,99 @@ The `rescue_class` config option specifies what to rescue. Set to StandardError
|
|
752
912
|
|
753
913
|
The `rescue_handlers` config option is like a minimalist set of rescue blocks that apply to every action method. For example, the following would effectively `rescue => e` (rescuing `StandardError`) and then for `ActiveRecord::RecordNotFound`, it would uses response status `:not_found` (HTTP 404). Otherwise it uses status `:internal_server_error` (HTTP 500). In both cases the error message is `e.message`:
|
754
914
|
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
]
|
915
|
+
```ruby
|
916
|
+
RestfulJson.configure do
|
917
|
+
self.rescue_class = StandardError
|
918
|
+
self.rescue_handlers = [
|
919
|
+
{exception_classes: [ActiveRecord::RecordNotFound], status: :not_found},
|
920
|
+
{status: :internal_server_error}
|
921
|
+
]
|
922
|
+
end
|
923
|
+
```
|
760
924
|
|
761
925
|
In a slightly more complicated case, this configuration would catch all exceptions raised with each actinon method that had `ActiveRecord::RecordNotFound` as an ancestor and use the error message defined by i18n key 'api.not_found'. All other exceptions would use status `:internal_server_error` (because it is a default, and doesn't have to be specified) but would use the error message defined by i18n key 'api.internal_server_error':
|
762
926
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
]
|
768
|
-
|
927
|
+
```ruby
|
928
|
+
RestfulJson.configure do
|
929
|
+
self.rescue_class = Exception
|
930
|
+
self.rescue_handlers = [
|
931
|
+
{exception_ancestor_classes: [ActiveRecord::RecordNotFound], status: :not_found, i18n_key: 'api.not_found'.freeze},
|
932
|
+
{i18n_key: 'api.internal_server_error'.freeze}
|
933
|
+
]
|
934
|
+
end
|
935
|
+
```
|
769
936
|
|
770
937
|
The `return_error_data` config option will not only return a response with `status` and `error` but also an `error_data` containing the `e.class.name`, `e.message`, and cleaned `e.backtrace`.
|
771
938
|
|
939
|
+
If you want to rescue using `rescue_from` in a controller or ApplicationController, let all errors and exceptions fall out of restful_json action methods with:
|
940
|
+
|
941
|
+
```ruby
|
942
|
+
RestfulJson.configure do
|
943
|
+
self.rescue_handlers = []
|
944
|
+
end
|
945
|
+
```
|
946
|
+
|
772
947
|
### Release Notes
|
773
948
|
|
774
|
-
|
949
|
+
See the [changelog][changelog] for basically what happened when, and git log for everything else.
|
950
|
+
|
951
|
+
### Upgrading
|
952
|
+
|
953
|
+
The class method:
|
954
|
+
|
955
|
+
```ruby
|
956
|
+
acts_as_restful_json
|
957
|
+
```
|
775
958
|
|
776
|
-
|
959
|
+
Which depended on [Permitters][permitters] for Rails 3.x can be replaced with:
|
777
960
|
|
778
|
-
|
961
|
+
```ruby
|
962
|
+
include RestfulJson::DefaultController
|
963
|
+
```
|
964
|
+
|
965
|
+
or if using Rails 4.x, you should consider including the modules separately so that you don't include the `ActionController::StrongParameters` module twice in a controller, for example.
|
966
|
+
|
967
|
+
Also, in past versions, everything was done to the models whether you wanted it done or not. Have been trying to transition away from forcing anything, so starting with v3.3, ensure the following is done.
|
968
|
+
|
969
|
+
If you are using Rails 3.1-3.2 and want to use Permitters or Strong Parameters in all models:
|
779
970
|
|
780
971
|
Make sure you include Strong Parameters:
|
781
972
|
|
782
|
-
|
973
|
+
```ruby
|
974
|
+
gem "strong_parameters"
|
975
|
+
```
|
783
976
|
|
784
977
|
Include this in `config/environment.rb`:
|
785
978
|
|
786
|
-
|
979
|
+
```ruby
|
980
|
+
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
|
981
|
+
```
|
787
982
|
|
788
|
-
If you want to use
|
983
|
+
If you want to use Permitters in all models, you need CanCan:
|
789
984
|
|
790
|
-
Make sure you include
|
985
|
+
Make sure you include CanCan:
|
791
986
|
|
792
|
-
|
987
|
+
```ruby
|
988
|
+
gem "cancan"
|
989
|
+
```
|
793
990
|
|
794
991
|
Include this in `config/environment.rb`
|
795
992
|
|
796
|
-
|
993
|
+
```ruby
|
994
|
+
ActiveRecord::Base.send(:include, CanCan::ModelAdditions)
|
995
|
+
```
|
996
|
+
|
997
|
+
Configuration, suggestions, and what to use and how may continue to change, but read this doc fully and hopefully it is correct!
|
797
998
|
|
798
999
|
### Rails Version-specific Eccentricities
|
799
1000
|
|
800
|
-
Strong Parameters
|
1001
|
+
Strong Parameters and JBuilder are included in Rails 4. You can use Permitters and ActiveModel::Serializers but for Permitters, you shouldn't define `gem 'strong_parameters'`.
|
801
1002
|
|
802
1003
|
If you are using Rails 3.1.x, note that respond_with returns HTTP 200 instead of 204 for update and destroy, unless return_resource is true.
|
803
1004
|
|
804
|
-
###
|
1005
|
+
### Contributing
|
805
1006
|
|
806
|
-
|
1007
|
+
Please fork, make changes in a separate branch, and do a pull request for your branch. Thanks!
|
807
1008
|
|
808
1009
|
### Contributors
|
809
1010
|
|
@@ -816,7 +1017,8 @@ Copyright (c) 2013 Gary S. Weaver, released under the [MIT license][lic].
|
|
816
1017
|
|
817
1018
|
[employee-training-tracker]: https://github.com/FineLinePrototyping/employee-training-tracker
|
818
1019
|
[built_with_angularjs]: http://builtwith.angularjs.org/
|
819
|
-
[
|
1020
|
+
[permitters]: https://github.com/permitters/permitters
|
1021
|
+
[jbuilder]: https://github.com/rails/jbuilder
|
820
1022
|
[cancan]: https://github.com/ryanb/cancan
|
821
1023
|
[strong_parameters]: https://github.com/rails/strong_parameters
|
822
1024
|
[active_model_serializers]: https://github.com/josevalim/active_model_serializers
|
@@ -828,4 +1030,5 @@ Copyright (c) 2013 Gary S. Weaver, released under the [MIT license][lic].
|
|
828
1030
|
[railscast320]: http://railscasts.com/episodes/320-jbuilder
|
829
1031
|
[public_exceptions]: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
|
830
1032
|
[show_exceptions]: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
|
1033
|
+
[changelog]: https://github.com/rubyservices/restful_json/blob/master/CHANGELOG.md
|
831
1034
|
[lic]: http://github.com/rubyservices/restful_json/blob/master/LICENSE
|