restful_json 3.4.1 → 3.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +831 -831
- data/lib/restful_json/default_controller.rb +0 -6
- data/lib/restful_json/version.rb +1 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -1,831 +1,831 @@
|
|
1
|
-
# Restful JSON [![Build Status](https://secure.travis-ci.org/rubyservices/restful_json.png?branch=master)](http://travis-ci.org/rubyservices/restful_json)
|
2
|
-
|
3
|
-
Develop declarative, featureful RESTful-ish JSON service controllers to use with modern Javascript MVC frameworks like AngularJS, Ember, etc. with much less code. (I say "RESTful-ish" instead of RESTful, to differentiate them from true-REST, hypermedia-driven projects, but restful_json controllers are RESTful by the existing Rails definition of being RESTful, using the same actions and resourceful routes, but with more abilities.)
|
4
|
-
|
5
|
-
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
|
-
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. That means your controllers will be easier to read and there will be less code to maintain, but when you need an action method more customized than we can provide, that's all you'll have to write.
|
8
|
-
|
9
|
-
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
|
-
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
|
-
You can use any of these for the JSON response (the view):
|
14
|
-
* [active_model_serializers][active_model_serializers] - also provides the serialize_action class method in the controller to specify custom serializers (assuming you are using a later version of active_model_serializers that works with respond_with).
|
15
|
-
* JBuilder - to use, set render_enabled in the restful_json config to false.
|
16
|
-
* Just about anything else that works with render/respond_with, or that just adjust the view like JBuilder, and don't require extra work in the controller.
|
17
|
-
|
18
|
-
And can use any of the following for authorizing parameters in the incoming JSON (for create/update):
|
19
|
-
* Adam Hawkins' [permitters][permitter] which use [Strong Parameters][strong_parameters] and [Cancan][cancan]. Permitters are an object-oriented way of defining what is permitted in the incoming JSON, and are a great compliment in the same way that ActiveModel::Serializers are. Cancan supports [Authlogic][authlogic], [Devise][devise], etc.
|
20
|
-
* [Strong Parameters][strong_parameters] - lets you only have to define `(single model name)_params` and/or `create_(single model name)_params` and/or `update_(single model name)_params` which can call require, permit, etc. on params.
|
21
|
-
* Mass assignment security in Rails 3.x (attr_accessible, etc.).
|
22
|
-
|
23
|
-
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
|
-
### Installation
|
26
|
-
|
27
|
-
In your Rails app's `Gemfile`:
|
28
|
-
|
29
|
-
gem 'restful_json', '~> 3.4.
|
30
|
-
|
31
|
-
And if you go with the defaults to use ActiveModel::Serializers and Permitters (using Strong Parameters and Cancan):
|
32
|
-
|
33
|
-
# comment this out if you don't want to use Strong Parameters or Permitters, or if you are using Rails 4, which includes it
|
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:
|
41
|
-
|
42
|
-
bundle install
|
43
|
-
|
44
|
-
#### Strong Parameters
|
45
|
-
|
46
|
-
Strong Parameters is not required, but can be used on its own or as a dependency of Permitters.
|
47
|
-
|
48
|
-
If you are using Rails 4.x, you might be able to skip this section, as [Strong Parameters][strong_parameters] is included.
|
49
|
-
|
50
|
-
If you are using Rails 3.x, then if you plan to use Permitters or want to use Strong Parameters by itself, you may need to tweak a few things for [Strong Parameters][strong_parameters]
|
51
|
-
|
52
|
-
To disable the default whitelisting that occurs in later versions of Rails 3.x, set the `config.active_record.whitelist_attributes` property in your `config/application.rb` to false:
|
53
|
-
|
54
|
-
config.active_record.whitelist_attributes = false
|
55
|
-
|
56
|
-
No more attr_accessible needed in your models (so take them out and convert them). Instead you will either put this information into your Permitters, or if you are using Strong Parameters without Permitters, you'll create `create_(single model name)_params`, `update_(single model name)_params`, and/or `(single model name)_params` methods in your controller(s). Encapsulating what params are permissible in such a method is encouraged and described in the [Strong Parameters][strong_parameters] documentation.
|
57
|
-
|
58
|
-
Strong Parameters (and Permitters) require a model include.
|
59
|
-
|
60
|
-
Put this in each model you want to use Strong Parameters with:
|
61
|
-
|
62
|
-
include ActiveModel::ForbiddenAttributesProtection
|
63
|
-
|
64
|
-
If you'd rather use Strong Parameters with all models, just put this in your `config/environment.rb`:
|
65
|
-
|
66
|
-
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
|
67
|
-
|
68
|
-
#### Cancan
|
69
|
-
|
70
|
-
Though optional, if you decide to use Permitters, the Permitters framework relies on [Cancan][cancan].
|
71
|
-
|
72
|
-
Permitters are an object-oriented representation of Strong Parameters, but they also integrate with Cancan. Cancan can restrict what resources a given user is allowed to access. In Cancan, all permissions are defined in a single location (the Ability class) and not duplicated across controllers, views, and database queries.
|
73
|
-
|
74
|
-
To setup Cancan, you need a `current_user` method in your `app/controllers/application_controller.rb` or in your service controllers. For the sake of example, we'll just have it return a new User:
|
75
|
-
|
76
|
-
class ApplicationController < ActionController::Base
|
77
|
-
protect_from_forgery
|
78
|
-
|
79
|
-
def current_user
|
80
|
-
User.new
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
Cancan integrates [Authlogic][authlogic], [Devise][devise], etc. to return a proper logged-in user or you can return it however you wish.
|
85
|
-
|
86
|
-
Cancan also needs an Ability defined in `app/models/ability.rb`. Just for testing we'll ignore the user object and allow everything:
|
87
|
-
|
88
|
-
class Ability
|
89
|
-
include CanCan::Ability
|
90
|
-
|
91
|
-
def initialize(user)
|
92
|
-
can :manage, :all
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
Put this in each model you want to use Cancan with:
|
97
|
-
|
98
|
-
include CanCan::ModelAdditions
|
99
|
-
|
100
|
-
Or, if you'd rather use Cancan with all models, just put this in your `config/environment.rb`:
|
101
|
-
|
102
|
-
ActiveRecord::Base.send(:include, CanCan::ModelAdditions)
|
103
|
-
|
104
|
-
Once you get everything setup, go through the [Cancan][cancan] documentation, and then [Authlogic][authlogic], [Devise][devise], etc. to setup/integrate with proper authentication and authorization.
|
105
|
-
|
106
|
-
#### JSON Response Generators
|
107
|
-
|
108
|
-
##### ActiveModel Serializers
|
109
|
-
|
110
|
-
Use of [ActiveModel::Serializers][active_model_serializers] is optional, but a great way to have object-oriented model-like representation of JSON views.
|
111
|
-
|
112
|
-
The purpose of ActiveModel::Serializers is to provide an object to encapsulate serialization of ActiveModel objects, including ActiveRecord objects. Serializers know about both a model and the current_user, so you can customize serialization based upon whether a user is authorized to see the content. In short, serializers replace hash-driven development with object-oriented development.
|
113
|
-
|
114
|
-
If you chose to use ActiveModel::Serializers, you'll eventually want to create one or more serializers for each model that you will be returning via the service(s), e.g.:
|
115
|
-
|
116
|
-
/app/serializers/singular_model_name_serializer.rb
|
117
|
-
|
118
|
-
Without having to do anything else, each restful_json controller will use `/app/serializers/singular_model_name_serializer.rb`, e.g. `/app/serializers/bar_serializer.rb` for the actions: index, show, new, create, update, destroy (not edit).
|
119
|
-
|
120
|
-
If you want to define a different serializer another action, e.g. the index action so that a list of instances has a different JSON format:
|
121
|
-
|
122
|
-
serialize_action :index, with: BarsSerializer
|
123
|
-
|
124
|
-
You can also use a specific format for multiple actions:
|
125
|
-
|
126
|
-
serialize_action :index, :my_other_list_action, with: BarsSerializer
|
127
|
-
|
128
|
-
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 (keep reading!).
|
129
|
-
|
130
|
-
You can just use the default serialization provided by AMS if you want. No class needed.
|
131
|
-
|
132
|
-
Because of some issues with some versions of ActiveModel::Serializers using respond_with, you might want to set the option:
|
133
|
-
|
134
|
-
RestfulJson.avoid_respond_with = true
|
135
|
-
|
136
|
-
Otherwise, custom serializers, etc. might not be used as intended.
|
137
|
-
|
138
|
-
##### JBuilder
|
139
|
-
|
140
|
-
If you want to use JBuilder instead to render, first:
|
141
|
-
|
142
|
-
gem 'jbuilder'
|
143
|
-
|
144
|
-
If you want to enable JBuilder for all restful_json services, you need to disable all renders and respond_withs in the controller:
|
145
|
-
|
146
|
-
RestfulJson.render_enabled = false
|
147
|
-
|
148
|
-
Or you can also just enable/disable rendering in a controller via setting `self.render_enabled`:
|
149
|
-
|
150
|
-
self.render_enabled = false
|
151
|
-
|
152
|
-
Then make sure you add a JBuilder view for each action you require. Although all may not be relevant, we support: index, show, new, edit, create, update, destroy. Maybe you'd create:
|
153
|
-
|
154
|
-
/app/views/plural_name_of_model/index.json.jbuilder
|
155
|
-
/app/views/plural_name_of_model/show.json.jbuilder
|
156
|
-
/app/views/plural_name_of_model/create.json.jbuilder
|
157
|
-
/app/views/plural_name_of_model/update.json.jbuilder
|
158
|
-
|
159
|
-
See [Railscast #320][railscast320] for more examples on setting up and using JBuilder.
|
160
|
-
|
161
|
-
##### Other Options
|
162
|
-
|
163
|
-
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
|
-
|
165
|
-
#### Create/Update JSON Request/Params Acceptance
|
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
|
174
|
-
|
175
|
-
Permitters use [Cancan][cancan] for authorization and [Strong Parameters][strong_parameters] for parameter permitting.
|
176
|
-
|
177
|
-
We have an implementation of ApplicationPermitter, so you just need permitters in `/app/permitters/`, e.g. `/app/permitters/foobar_permitter.rb`:
|
178
|
-
|
179
|
-
class FoobarPermitter < ApplicationPermitter
|
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
|
189
|
-
|
190
|
-
If you don't accept anything in create/update, you should have an empty Permitter for the model:
|
191
|
-
|
192
|
-
class FoobarPermitter < ApplicationPermitter
|
193
|
-
end
|
194
|
-
|
195
|
-
##### Strong Parameters
|
196
|
-
|
197
|
-
To use strong_parameters by themselves, without Permitters/Cancan, specify this in restful_json config/controller config:
|
198
|
-
|
199
|
-
self.use_permitters = false
|
200
|
-
|
201
|
-
As noted in [Strong Parameters][strong_parameters], it is suggested to encapsulate the permitting into a private method in the controller, so we've taken that to heart and the controller just attempts to call the relevant *_params method or create_*_params/update_*_params, e.g. in the controller:
|
202
|
-
|
203
|
-
def foobar_params
|
204
|
-
params.require(:foobar).permit(:name, :age)
|
205
|
-
end
|
206
|
-
|
207
|
-
##### Mass Assignment Security
|
208
|
-
|
209
|
-
To use mass assignment security in Rails 3.x, specify this in restful_json config/controller config:
|
210
|
-
|
211
|
-
self.use_permitters = false
|
212
|
-
|
213
|
-
Don't use any of these, as they each include Strong Parameters:
|
214
|
-
|
215
|
-
include ActionController::StrongParameters
|
216
|
-
include RestfulJson::DefaultController
|
217
|
-
acts_as_restful_json
|
218
|
-
|
219
|
-
Only the main controller is needed:
|
220
|
-
|
221
|
-
include RestfulJson::Controller
|
222
|
-
|
223
|
-
Then, make sure that attr_accessible and/or attr_protected, etc. are used properly.
|
224
|
-
|
225
|
-
### Application Configuration
|
226
|
-
|
227
|
-
At the bottom of `config/environment.rb`, you can set restful_json can be configured one line at a time.
|
228
|
-
|
229
|
-
RestfulJson.debug = true
|
230
|
-
|
231
|
-
or in bulk, like:
|
232
|
-
|
233
|
-
RestfulJson.configure do
|
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
|
263
|
-
|
264
|
-
### Controller Configuration
|
265
|
-
|
266
|
-
In the controller, you can set a variety of class attributes with `self.something = ...` in the body of your controller.
|
267
|
-
|
268
|
-
All of the app-level configuration parameters are configurable at the controller level:
|
269
|
-
|
270
|
-
self.can_filter_by_default_using = [:eq]
|
271
|
-
self.debug = false
|
272
|
-
self.filter_split = ','
|
273
|
-
self.formats = :json, :html
|
274
|
-
self.number_of_records_in_a_page = 15
|
275
|
-
self.predicate_prefix = '!'
|
276
|
-
self.return_resource = false
|
277
|
-
self.render_enabled = true
|
278
|
-
self.use_permitters = true
|
279
|
-
|
280
|
-
In addition there are some that are controller-only...
|
281
|
-
|
282
|
-
If you don't use the standard controller naming convention, you can define this in the controller:
|
283
|
-
|
284
|
-
self.model_class = YourModel
|
285
|
-
|
286
|
-
If it doesn't handle the other forms well, you can explicitly define the singular/plural names:
|
287
|
-
|
288
|
-
self.model_singular_name = 'your_model'
|
289
|
-
self.model_plural_name = 'your_models'
|
290
|
-
|
291
|
-
These are used for *_url method definitions, to set instance variables like `@foobar` and `@foobars` dynamically, etc.
|
292
|
-
|
293
|
-
Other class attributes are available for setting/overriding, but they are all set by the other class methods defined in the next section.
|
294
|
-
|
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
|
-
#### Routing
|
344
|
-
|
345
|
-
You can just add normal Rails RESTful routes in `config/routes.rb`, e.g. for the Foobar model:
|
346
|
-
|
347
|
-
MyAppName::Application.routes.draw do
|
348
|
-
resources :foobars
|
349
|
-
end
|
350
|
-
|
351
|
-
Supports static, nested, etc. routes also, e.g.:
|
352
|
-
|
353
|
-
MyAppName::Application.routes.draw do
|
354
|
-
namespace :my_service_controller_module do
|
355
|
-
resources :foobars
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
Can pass in params from the path for use in filters, etc. as if they were request parameters:
|
360
|
-
|
361
|
-
MyAppName::Application.routes.draw do
|
362
|
-
namespace :my_service_controller_module do
|
363
|
-
match 'bar/:bar_id/foobars(.:format)' => 'foobars#index'
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
#### Default Filtering by Attribute(s)
|
368
|
-
|
369
|
-
First, declare in the controller:
|
370
|
-
|
371
|
-
can_filter_by :foo_id
|
372
|
-
|
373
|
-
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
|
-
|
375
|
-
http://localhost:3000/foobars?foo_id=1
|
376
|
-
|
377
|
-
`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
|
-
|
379
|
-
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
|
380
|
-
|
381
|
-
`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
|
-
|
383
|
-
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})}
|
384
|
-
|
385
|
-
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
|
-
|
387
|
-
can_filter_by :a_request_param_name, through: [:some_assoc, :some_attr]
|
388
|
-
|
389
|
-
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
|
-
|
391
|
-
can_filter_by :magical_unicorn_name, with_query: ->(t,q,param_value) {q.joins(:magical_unicorns).where(:magical_unicorns=>{name: param_value})}
|
392
|
-
|
393
|
-
or:
|
394
|
-
|
395
|
-
can_filter_by :magical_unicorn_name, through: [:magical_unicorns, :name]
|
396
|
-
|
397
|
-
and you can then use this:
|
398
|
-
|
399
|
-
http://localhost:3000/magical_valleys?magical_unicorn_name=Rainbow
|
400
|
-
|
401
|
-
or if a MagicalUnicorn `has_many :friends` and a MagicalUnicorn's friend has a name attribute:
|
402
|
-
|
403
|
-
can_filter_by :magical_unicorn_friend_name, through: [:magical_unicorns, :friends, :name]
|
404
|
-
|
405
|
-
and use this to get valleys associated with unicorns who in turn have a friend named Oscar:
|
406
|
-
|
407
|
-
http://localhost:3000/magical_valleys?magical_unicorn_friend_name=Oscar
|
408
|
-
|
409
|
-
#### Other Filters by Attribute(s)
|
410
|
-
|
411
|
-
First, declare in the controller:
|
412
|
-
|
413
|
-
can_filter_by :seen_on, using: [:gteq, :eq_any]
|
414
|
-
|
415
|
-
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
|
-
|
417
|
-
http://localhost:3000/foobars?seen_on!gteq=2012-08-08
|
418
|
-
|
419
|
-
Multiple values are separated by `filter_split` (configurable):
|
420
|
-
|
421
|
-
http://localhost:3000/foobars?seen_on!eq_any=2012-08-08,2012-09-09
|
422
|
-
|
423
|
-
#### Supported Functions
|
424
|
-
|
425
|
-
##### Declaring
|
426
|
-
|
427
|
-
`supports_functions` lets you allow the [ARel][arel] functions: `:uniq`, `:skip`, `:take`, and/or `:count`.
|
428
|
-
|
429
|
-
##### Unique (DISTINCT)
|
430
|
-
|
431
|
-
First, declare in the controller:
|
432
|
-
|
433
|
-
supports_functions :uniq
|
434
|
-
|
435
|
-
Now this works:
|
436
|
-
|
437
|
-
http://localhost:3000/foobars?uniq=
|
438
|
-
|
439
|
-
##### Count
|
440
|
-
|
441
|
-
First, declare in the controller:
|
442
|
-
|
443
|
-
supports_functions :count
|
444
|
-
|
445
|
-
Now this works:
|
446
|
-
|
447
|
-
http://localhost:3000/foobars?count=
|
448
|
-
|
449
|
-
##### Paging
|
450
|
-
|
451
|
-
First, declare in the controller:
|
452
|
-
|
453
|
-
supports_functions :page, :page_count
|
454
|
-
|
455
|
-
Now you can get the page count:
|
456
|
-
|
457
|
-
http://localhost:3000/foobars?page_count=
|
458
|
-
|
459
|
-
And access each page of results:
|
460
|
-
|
461
|
-
http://localhost:3000/foobars?page=1
|
462
|
-
http://localhost:3000/foobars?page=2
|
463
|
-
...
|
464
|
-
|
465
|
-
To set page size at application level:
|
466
|
-
|
467
|
-
RestfulJson.number_of_records_in_a_page = 15
|
468
|
-
|
469
|
-
To set page size at controller level:
|
470
|
-
|
471
|
-
self.number_of_records_in_a_page = 15
|
472
|
-
|
473
|
-
##### Skip and Take (OFFSET and LIMIT)
|
474
|
-
|
475
|
-
First, declare in the controller:
|
476
|
-
|
477
|
-
supports_functions :skip, :take
|
478
|
-
|
479
|
-
To skip rows returned, use 'skip'. It is called take, because skip is the [ARel][arel] equivalent of SQL OFFSET:
|
480
|
-
|
481
|
-
http://localhost:3000/foobars?skip=5
|
482
|
-
|
483
|
-
To limit the number of rows returned, use 'take'. It is called take, because take is the [ARel][arel] equivalent of SQL LIMIT:
|
484
|
-
|
485
|
-
http://localhost:3000/foobars.json?take=5
|
486
|
-
|
487
|
-
Combine skip and take for manual completely customized paging, e.g.
|
488
|
-
|
489
|
-
http://localhost:3000/foobars?take=15
|
490
|
-
http://localhost:3000/foobars?skip=15&take=15
|
491
|
-
http://localhost:3000/foobars?skip=30&take=15
|
492
|
-
|
493
|
-
##### Custom Serializers
|
494
|
-
|
495
|
-
If using ActiveModel::Serializers, you can use something other than the `(singular model name)Serializer` via `serialize_action`:
|
496
|
-
|
497
|
-
serialize_action :index, with: ListFoobarSerializer
|
498
|
-
|
499
|
-
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
|
-
|
501
|
-
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
|
-
|
503
|
-
serialize_action :index, :some_custom_action, with: FoosSerializer, for: :array
|
504
|
-
|
505
|
-
Or, you could just use the default serialization, if you want.
|
506
|
-
|
507
|
-
##### Custom Queries
|
508
|
-
|
509
|
-
To filter the list where the status_code attribute is 'green':
|
510
|
-
|
511
|
-
# t is self.model_class.arel_table and q is self.model_class.scoped
|
512
|
-
query_for :index, is: lambda {|t,q| q.where(:status_code => 'green')}
|
513
|
-
|
514
|
-
or use the `->` Ruby 1.9 lambda stab operator (note lack of whitespace between stab and parenthesis):
|
515
|
-
|
516
|
-
# t is self.model_class.arel_table and q is self.model_class.scoped
|
517
|
-
query_for :index, is: is: ->(t,q) {q.where(:status_code => 'green')}
|
518
|
-
|
519
|
-
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:
|
520
|
-
|
521
|
-
# t is self.model_class.arel_table and q is self.model_class.scoped
|
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!
|
532
|
-
|
533
|
-
However `query_for` will create new action methods, so you can easily create custom non-RESTful action methods:
|
534
|
-
|
535
|
-
# t is self.model_class.arel_table and q is self.model_class.scoped
|
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')}
|
538
|
-
|
539
|
-
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.
|
540
|
-
|
541
|
-
query_for :some_action, is: ->(t,q) do
|
542
|
-
if @current_user.admin?
|
543
|
-
Rails.logger.debug("Notice: unfiltered results provided to admin #{@current_user.name}")
|
544
|
-
# just make sure the relation is returned!
|
545
|
-
q
|
546
|
-
else
|
547
|
-
q.where(:access => 'public')
|
548
|
-
end
|
549
|
-
end
|
550
|
-
|
551
|
-
Be sure to add a route for that action, e.g. in `config/routes.rb`, e.g. for the Barfoo model:
|
552
|
-
|
553
|
-
MyAppName::Application.routes.draw do
|
554
|
-
resources :barfoos do
|
555
|
-
get 'some_action', :on => :collection
|
556
|
-
end
|
557
|
-
end
|
558
|
-
|
559
|
-
### With Rails-api
|
560
|
-
|
561
|
-
If you want to try out [rails-api][rails-api]:
|
562
|
-
|
563
|
-
gem 'rails-api', '~> 0.0.3'
|
564
|
-
|
565
|
-
In `app/controllers/my_service_controller.rb`:
|
566
|
-
|
567
|
-
module MyServiceController
|
568
|
-
extend ActiveSupport::Concern
|
569
|
-
|
570
|
-
included do
|
571
|
-
# Rails-api lets you choose features. You might not need all of these, or may need others.
|
572
|
-
include AbstractController::Translation
|
573
|
-
include ActionController::HttpAuthentication::Basic::ControllerMethods
|
574
|
-
include AbstractController::Layouts
|
575
|
-
include ActionController::MimeResponds
|
576
|
-
include ActionController::Cookies
|
577
|
-
include ActionController::ParamsWrapper
|
578
|
-
|
579
|
-
# use Permitters and AMS
|
580
|
-
include RestfulJson::DefaultController
|
581
|
-
# or comment that last line and uncomment whatever you want to use
|
582
|
-
#include ::ActionController::Serialization # AMS
|
583
|
-
#include ::ActionController::StrongParameters
|
584
|
-
#include ::TwinTurbo::Controller # Permitters which uses Cancan and Strong Parameters
|
585
|
-
#include ::RestfulJson::Controller
|
586
|
-
|
587
|
-
# If you want any additional inline class stuff, it goes here...
|
588
|
-
end
|
589
|
-
end
|
590
|
-
|
591
|
-
class FoobarsController < ActionController::API
|
592
|
-
include MyServiceController
|
593
|
-
end
|
594
|
-
|
595
|
-
class BarfoosController < ActionController::API
|
596
|
-
include MyServiceController
|
597
|
-
end
|
598
|
-
|
599
|
-
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
|
-
|
601
|
-
ActiveSupport.on_load(:action_controller) do
|
602
|
-
# without include of ParamsWrapper, will get undefined method `wrap_parameters' for ActionController::API:Class (NoMethodError)
|
603
|
-
include ActionController::ParamsWrapper
|
604
|
-
# in this case it's expecting unwrapped params, but we could maybe use wrap_parameters format: [:json]
|
605
|
-
wrap_parameters format: []
|
606
|
-
end
|
607
|
-
|
608
|
-
# Disable root element in JSON by default.
|
609
|
-
ActiveSupport.on_load(:active_record) do
|
610
|
-
self.include_root_in_json = false
|
611
|
-
end
|
612
|
-
|
613
|
-
### Refactoring and Customing the Default Behavior
|
614
|
-
|
615
|
-
##### Parent/Ancestor Class Definition Not Supported
|
616
|
-
|
617
|
-
Don't subclass and include in the parent, that puts the class attributes into the parent which means they would be shared by the children and bad things can happen.
|
618
|
-
|
619
|
-
Don't do this:
|
620
|
-
|
621
|
-
class ServiceController < ApplicationController
|
622
|
-
include ::ActionController::Serialization
|
623
|
-
include ::ActionController::StrongParameters
|
624
|
-
include ::TwinTurbo::Controller
|
625
|
-
include ::RestfulJson::Controller
|
626
|
-
end
|
627
|
-
|
628
|
-
class FoobarsController < ServiceController
|
629
|
-
end
|
630
|
-
|
631
|
-
class BarfoosController < ServiceController
|
632
|
-
end
|
633
|
-
|
634
|
-
And don't do this:
|
635
|
-
|
636
|
-
class FoobarsController < ApplicationController
|
637
|
-
include RestfulJson::DefaultController
|
638
|
-
end
|
639
|
-
|
640
|
-
class FoobarsController < ServiceController
|
641
|
-
end
|
642
|
-
|
643
|
-
class BarfoosController < ServiceController
|
644
|
-
end
|
645
|
-
|
646
|
-
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
|
-
|
648
|
-
##### Customizing Behavior via Patch
|
649
|
-
|
650
|
-
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
|
-
|
652
|
-
# a horrible Hello World example
|
653
|
-
module RestfulJson
|
654
|
-
module Controller
|
655
|
-
|
656
|
-
# class methods that should be implemented or overriden
|
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
|
681
|
-
|
682
|
-
end
|
683
|
-
end
|
684
|
-
|
685
|
-
Now in your controller, if you:
|
686
|
-
|
687
|
-
class FoobarsController < ApplicationController
|
688
|
-
include RestfulJson::DefaultController
|
689
|
-
hello 'world'
|
690
|
-
end
|
691
|
-
|
692
|
-
(Note again: RestfulJson::DefaultController includes RestfulJson::Controller.)
|
693
|
-
|
694
|
-
Now when you call:
|
695
|
-
|
696
|
-
http://localhost:3000/foobars
|
697
|
-
|
698
|
-
You would get the response:
|
699
|
-
|
700
|
-
{'hello': 'world'}
|
701
|
-
|
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.
|
703
|
-
|
704
|
-
### Error Handling
|
705
|
-
|
706
|
-
#### Properly Handling Non-controller-action Errors
|
707
|
-
|
708
|
-
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
|
-
|
710
|
-
Rails 4 has basic error handling defined in the [public_exceptions][public_exceptions] and [show_exceptions][show_exceptions] Rack middleware.
|
711
|
-
|
712
|
-
Rails 3.2.x has support for `config.exceptions_app` which can be defined as the following to simulate Rails 4 exception handling:
|
713
|
-
|
714
|
-
config.exceptions_app = lambda do |env|
|
715
|
-
exception = env["action_dispatch.exception"]
|
716
|
-
status = env["PATH_INFO"][1..-1]
|
717
|
-
request = ActionDispatch::Request.new(env)
|
718
|
-
content_type = request.formats.first
|
719
|
-
body = { :status => status, :error => exception.message }
|
720
|
-
format = content_type && "to_#{content_type.to_sym}"
|
721
|
-
if format && body.respond_to?(format)
|
722
|
-
formatted_body = body.public_send(format)
|
723
|
-
[status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
|
724
|
-
'Content-Length' => body.bytesize.to_s}, [formatted_body]]
|
725
|
-
else
|
726
|
-
found = false
|
727
|
-
path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
|
728
|
-
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
|
729
|
-
|
730
|
-
if found || File.exist?(path)
|
731
|
-
[status, {'Content-Type' => "text/html; charset=#{ActionDispatch::Response.default_charset}",
|
732
|
-
'Content-Length' => body.bytesize.to_s}, [File.read(path)]]
|
733
|
-
else
|
734
|
-
[404, { "X-Cascade" => "pass" }, []]
|
735
|
-
end
|
736
|
-
end
|
737
|
-
end
|
738
|
-
|
739
|
-
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
|
-
|
741
|
-
Unfortunately, this doesn't work for Rails 3.1.x. However, in many scenarios there is the chance at a rare situation when the proper format is not returned to the client, even if everything is controlled as much as possible on the server. So, the client really needs to be able to handle such a case of unexpected format with a generic error.
|
742
|
-
|
743
|
-
But, if you can make Rack respond a little better for some errors, that's great.
|
744
|
-
|
745
|
-
#### Controller Error Handling Configuration
|
746
|
-
|
747
|
-
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
|
-
|
749
|
-
There are a few options to customize the rescue and error rendering behavior.
|
750
|
-
|
751
|
-
The `rescue_class` config option specifies what to rescue. Set to StandardError to behave like a normal rescue. Set to nil to just reraise everything rescued (to disable handling).
|
752
|
-
|
753
|
-
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
|
-
|
755
|
-
self.rescue_class = StandardError
|
756
|
-
self.rescue_handlers = [
|
757
|
-
{exception_classes: [ActiveRecord::RecordNotFound], status: :not_found},
|
758
|
-
{status: :internal_server_error}
|
759
|
-
]
|
760
|
-
|
761
|
-
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
|
-
|
763
|
-
self.rescue_class = Exception
|
764
|
-
self.rescue_handlers = [
|
765
|
-
{exception_ancestor_classes: [ActiveRecord::RecordNotFound], status: :not_found, i18n_key: 'api.not_found'.freeze},
|
766
|
-
{i18n_key: 'api.internal_server_error'.freeze}
|
767
|
-
]
|
768
|
-
|
769
|
-
|
770
|
-
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
|
-
|
772
|
-
### Release Notes
|
773
|
-
|
774
|
-
#### restful_json v3.3
|
775
|
-
|
776
|
-
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.
|
777
|
-
|
778
|
-
If you are using Rails 3.1-3.2 and want to use permitters or strong_parameters in all models:
|
779
|
-
|
780
|
-
Make sure you include Strong Parameters:
|
781
|
-
|
782
|
-
gem "strong_parameters"
|
783
|
-
|
784
|
-
Include this in `config/environment.rb`:
|
785
|
-
|
786
|
-
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
|
787
|
-
|
788
|
-
If you want to use permitters in all models, you need Cancan:
|
789
|
-
|
790
|
-
Make sure you include Cancan:
|
791
|
-
|
792
|
-
gem "cancan"
|
793
|
-
|
794
|
-
Include this in `config/environment.rb`
|
795
|
-
|
796
|
-
ActiveRecord::Base.send(:include, CanCan::ModelAdditions)
|
797
|
-
|
798
|
-
### Rails Version-specific Eccentricities
|
799
|
-
|
800
|
-
Strong Parameters is included in Rails 4.
|
801
|
-
|
802
|
-
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
|
-
|
804
|
-
### Thanks!
|
805
|
-
|
806
|
-
Without our users, where would we be? Feedback, bug reports, and code/documentation contributions are always welcome!
|
807
|
-
|
808
|
-
### Contributors
|
809
|
-
|
810
|
-
* Gary Weaver (https://github.com/garysweaver)
|
811
|
-
* Tommy Odom (https://github.com/tpodom)
|
812
|
-
|
813
|
-
### License
|
814
|
-
|
815
|
-
Copyright (c) 2013 Gary S. Weaver, released under the [MIT license][lic].
|
816
|
-
|
817
|
-
[employee-training-tracker]: https://github.com/FineLinePrototyping/employee-training-tracker
|
818
|
-
[built_with_angularjs]: http://builtwith.angularjs.org/
|
819
|
-
[permitter]: http://broadcastingadam.com/2012/07/parameter_authorization_in_rails_apis/
|
820
|
-
[cancan]: https://github.com/ryanb/cancan
|
821
|
-
[strong_parameters]: https://github.com/rails/strong_parameters
|
822
|
-
[active_model_serializers]: https://github.com/josevalim/active_model_serializers
|
823
|
-
[authlogic]: https://github.com/binarylogic/authlogic
|
824
|
-
[devise]: https://github.com/plataformatec/devise
|
825
|
-
[arel]: https://github.com/rails/arel
|
826
|
-
[ar]: http://api.rubyonrails.org/classes/ActiveRecord/Relation.html
|
827
|
-
[rails-api]: https://github.com/rails-api/rails-api
|
828
|
-
[railscast320]: http://railscasts.com/episodes/320-jbuilder
|
829
|
-
[public_exceptions]: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
|
830
|
-
[show_exceptions]: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
|
831
|
-
[lic]: http://github.com/rubyservices/restful_json/blob/master/LICENSE
|
1
|
+
# Restful JSON [![Build Status](https://secure.travis-ci.org/rubyservices/restful_json.png?branch=master)](http://travis-ci.org/rubyservices/restful_json)
|
2
|
+
|
3
|
+
Develop declarative, featureful RESTful-ish JSON service controllers to use with modern Javascript MVC frameworks like AngularJS, Ember, etc. with much less code. (I say "RESTful-ish" instead of RESTful, to differentiate them from true-REST, hypermedia-driven projects, but restful_json controllers are RESTful by the existing Rails definition of being RESTful, using the same actions and resourceful routes, but with more abilities.)
|
4
|
+
|
5
|
+
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
|
+
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. That means your controllers will be easier to read and there will be less code to maintain, but when you need an action method more customized than we can provide, that's all you'll have to write.
|
8
|
+
|
9
|
+
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
|
+
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
|
+
You can use any of these for the JSON response (the view):
|
14
|
+
* [active_model_serializers][active_model_serializers] - also provides the serialize_action class method in the controller to specify custom serializers (assuming you are using a later version of active_model_serializers that works with respond_with).
|
15
|
+
* JBuilder - to use, set render_enabled in the restful_json config to false.
|
16
|
+
* Just about anything else that works with render/respond_with, or that just adjust the view like JBuilder, and don't require extra work in the controller.
|
17
|
+
|
18
|
+
And can use any of the following for authorizing parameters in the incoming JSON (for create/update):
|
19
|
+
* Adam Hawkins' [permitters][permitter] which use [Strong Parameters][strong_parameters] and [Cancan][cancan]. Permitters are an object-oriented way of defining what is permitted in the incoming JSON, and are a great compliment in the same way that ActiveModel::Serializers are. Cancan supports [Authlogic][authlogic], [Devise][devise], etc.
|
20
|
+
* [Strong Parameters][strong_parameters] - lets you only have to define `(single model name)_params` and/or `create_(single model name)_params` and/or `update_(single model name)_params` which can call require, permit, etc. on params.
|
21
|
+
* Mass assignment security in Rails 3.x (attr_accessible, etc.).
|
22
|
+
|
23
|
+
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
|
+
### Installation
|
26
|
+
|
27
|
+
In your Rails app's `Gemfile`:
|
28
|
+
|
29
|
+
gem 'restful_json', '~> 3.4.2'
|
30
|
+
|
31
|
+
And if you go with the defaults to use ActiveModel::Serializers and Permitters (using Strong Parameters and Cancan):
|
32
|
+
|
33
|
+
# comment this out if you don't want to use Strong Parameters or Permitters, or if you are using Rails 4, which includes it
|
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:
|
41
|
+
|
42
|
+
bundle install
|
43
|
+
|
44
|
+
#### Strong Parameters
|
45
|
+
|
46
|
+
Strong Parameters is not required, but can be used on its own or as a dependency of Permitters.
|
47
|
+
|
48
|
+
If you are using Rails 4.x, you might be able to skip this section, as [Strong Parameters][strong_parameters] is included.
|
49
|
+
|
50
|
+
If you are using Rails 3.x, then if you plan to use Permitters or want to use Strong Parameters by itself, you may need to tweak a few things for [Strong Parameters][strong_parameters]
|
51
|
+
|
52
|
+
To disable the default whitelisting that occurs in later versions of Rails 3.x, set the `config.active_record.whitelist_attributes` property in your `config/application.rb` to false:
|
53
|
+
|
54
|
+
config.active_record.whitelist_attributes = false
|
55
|
+
|
56
|
+
No more attr_accessible needed in your models (so take them out and convert them). Instead you will either put this information into your Permitters, or if you are using Strong Parameters without Permitters, you'll create `create_(single model name)_params`, `update_(single model name)_params`, and/or `(single model name)_params` methods in your controller(s). Encapsulating what params are permissible in such a method is encouraged and described in the [Strong Parameters][strong_parameters] documentation.
|
57
|
+
|
58
|
+
Strong Parameters (and Permitters) require a model include.
|
59
|
+
|
60
|
+
Put this in each model you want to use Strong Parameters with:
|
61
|
+
|
62
|
+
include ActiveModel::ForbiddenAttributesProtection
|
63
|
+
|
64
|
+
If you'd rather use Strong Parameters with all models, just put this in your `config/environment.rb`:
|
65
|
+
|
66
|
+
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
|
67
|
+
|
68
|
+
#### Cancan
|
69
|
+
|
70
|
+
Though optional, if you decide to use Permitters, the Permitters framework relies on [Cancan][cancan].
|
71
|
+
|
72
|
+
Permitters are an object-oriented representation of Strong Parameters, but they also integrate with Cancan. Cancan can restrict what resources a given user is allowed to access. In Cancan, all permissions are defined in a single location (the Ability class) and not duplicated across controllers, views, and database queries.
|
73
|
+
|
74
|
+
To setup Cancan, you need a `current_user` method in your `app/controllers/application_controller.rb` or in your service controllers. For the sake of example, we'll just have it return a new User:
|
75
|
+
|
76
|
+
class ApplicationController < ActionController::Base
|
77
|
+
protect_from_forgery
|
78
|
+
|
79
|
+
def current_user
|
80
|
+
User.new
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Cancan integrates [Authlogic][authlogic], [Devise][devise], etc. to return a proper logged-in user or you can return it however you wish.
|
85
|
+
|
86
|
+
Cancan also needs an Ability defined in `app/models/ability.rb`. Just for testing we'll ignore the user object and allow everything:
|
87
|
+
|
88
|
+
class Ability
|
89
|
+
include CanCan::Ability
|
90
|
+
|
91
|
+
def initialize(user)
|
92
|
+
can :manage, :all
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
Put this in each model you want to use Cancan with:
|
97
|
+
|
98
|
+
include CanCan::ModelAdditions
|
99
|
+
|
100
|
+
Or, if you'd rather use Cancan with all models, just put this in your `config/environment.rb`:
|
101
|
+
|
102
|
+
ActiveRecord::Base.send(:include, CanCan::ModelAdditions)
|
103
|
+
|
104
|
+
Once you get everything setup, go through the [Cancan][cancan] documentation, and then [Authlogic][authlogic], [Devise][devise], etc. to setup/integrate with proper authentication and authorization.
|
105
|
+
|
106
|
+
#### JSON Response Generators
|
107
|
+
|
108
|
+
##### ActiveModel Serializers
|
109
|
+
|
110
|
+
Use of [ActiveModel::Serializers][active_model_serializers] is optional, but a great way to have object-oriented model-like representation of JSON views.
|
111
|
+
|
112
|
+
The purpose of ActiveModel::Serializers is to provide an object to encapsulate serialization of ActiveModel objects, including ActiveRecord objects. Serializers know about both a model and the current_user, so you can customize serialization based upon whether a user is authorized to see the content. In short, serializers replace hash-driven development with object-oriented development.
|
113
|
+
|
114
|
+
If you chose to use ActiveModel::Serializers, you'll eventually want to create one or more serializers for each model that you will be returning via the service(s), e.g.:
|
115
|
+
|
116
|
+
/app/serializers/singular_model_name_serializer.rb
|
117
|
+
|
118
|
+
Without having to do anything else, each restful_json controller will use `/app/serializers/singular_model_name_serializer.rb`, e.g. `/app/serializers/bar_serializer.rb` for the actions: index, show, new, create, update, destroy (not edit).
|
119
|
+
|
120
|
+
If you want to define a different serializer another action, e.g. the index action so that a list of instances has a different JSON format:
|
121
|
+
|
122
|
+
serialize_action :index, with: BarsSerializer
|
123
|
+
|
124
|
+
You can also use a specific format for multiple actions:
|
125
|
+
|
126
|
+
serialize_action :index, :my_other_list_action, with: BarsSerializer
|
127
|
+
|
128
|
+
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 (keep reading!).
|
129
|
+
|
130
|
+
You can just use the default serialization provided by AMS if you want. No class needed.
|
131
|
+
|
132
|
+
Because of some issues with some versions of ActiveModel::Serializers using respond_with, you might want to set the option:
|
133
|
+
|
134
|
+
RestfulJson.avoid_respond_with = true
|
135
|
+
|
136
|
+
Otherwise, custom serializers, etc. might not be used as intended.
|
137
|
+
|
138
|
+
##### JBuilder
|
139
|
+
|
140
|
+
If you want to use JBuilder instead to render, first:
|
141
|
+
|
142
|
+
gem 'jbuilder'
|
143
|
+
|
144
|
+
If you want to enable JBuilder for all restful_json services, you need to disable all renders and respond_withs in the controller:
|
145
|
+
|
146
|
+
RestfulJson.render_enabled = false
|
147
|
+
|
148
|
+
Or you can also just enable/disable rendering in a controller via setting `self.render_enabled`:
|
149
|
+
|
150
|
+
self.render_enabled = false
|
151
|
+
|
152
|
+
Then make sure you add a JBuilder view for each action you require. Although all may not be relevant, we support: index, show, new, edit, create, update, destroy. Maybe you'd create:
|
153
|
+
|
154
|
+
/app/views/plural_name_of_model/index.json.jbuilder
|
155
|
+
/app/views/plural_name_of_model/show.json.jbuilder
|
156
|
+
/app/views/plural_name_of_model/create.json.jbuilder
|
157
|
+
/app/views/plural_name_of_model/update.json.jbuilder
|
158
|
+
|
159
|
+
See [Railscast #320][railscast320] for more examples on setting up and using JBuilder.
|
160
|
+
|
161
|
+
##### Other Options
|
162
|
+
|
163
|
+
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
|
+
|
165
|
+
#### Create/Update JSON Request/Params Acceptance
|
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
|
174
|
+
|
175
|
+
Permitters use [Cancan][cancan] for authorization and [Strong Parameters][strong_parameters] for parameter permitting.
|
176
|
+
|
177
|
+
We have an implementation of ApplicationPermitter, so you just need permitters in `/app/permitters/`, e.g. `/app/permitters/foobar_permitter.rb`:
|
178
|
+
|
179
|
+
class FoobarPermitter < ApplicationPermitter
|
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
|
189
|
+
|
190
|
+
If you don't accept anything in create/update, you should have an empty Permitter for the model:
|
191
|
+
|
192
|
+
class FoobarPermitter < ApplicationPermitter
|
193
|
+
end
|
194
|
+
|
195
|
+
##### Strong Parameters
|
196
|
+
|
197
|
+
To use strong_parameters by themselves, without Permitters/Cancan, specify this in restful_json config/controller config:
|
198
|
+
|
199
|
+
self.use_permitters = false
|
200
|
+
|
201
|
+
As noted in [Strong Parameters][strong_parameters], it is suggested to encapsulate the permitting into a private method in the controller, so we've taken that to heart and the controller just attempts to call the relevant *_params method or create_*_params/update_*_params, e.g. in the controller:
|
202
|
+
|
203
|
+
def foobar_params
|
204
|
+
params.require(:foobar).permit(:name, :age)
|
205
|
+
end
|
206
|
+
|
207
|
+
##### Mass Assignment Security
|
208
|
+
|
209
|
+
To use mass assignment security in Rails 3.x, specify this in restful_json config/controller config:
|
210
|
+
|
211
|
+
self.use_permitters = false
|
212
|
+
|
213
|
+
Don't use any of these, as they each include Strong Parameters:
|
214
|
+
|
215
|
+
include ActionController::StrongParameters
|
216
|
+
include RestfulJson::DefaultController
|
217
|
+
acts_as_restful_json
|
218
|
+
|
219
|
+
Only the main controller is needed:
|
220
|
+
|
221
|
+
include RestfulJson::Controller
|
222
|
+
|
223
|
+
Then, make sure that attr_accessible and/or attr_protected, etc. are used properly.
|
224
|
+
|
225
|
+
### Application Configuration
|
226
|
+
|
227
|
+
At the bottom of `config/environment.rb`, you can set restful_json can be configured one line at a time.
|
228
|
+
|
229
|
+
RestfulJson.debug = true
|
230
|
+
|
231
|
+
or in bulk, like:
|
232
|
+
|
233
|
+
RestfulJson.configure do
|
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
|
263
|
+
|
264
|
+
### Controller Configuration
|
265
|
+
|
266
|
+
In the controller, you can set a variety of class attributes with `self.something = ...` in the body of your controller.
|
267
|
+
|
268
|
+
All of the app-level configuration parameters are configurable at the controller level:
|
269
|
+
|
270
|
+
self.can_filter_by_default_using = [:eq]
|
271
|
+
self.debug = false
|
272
|
+
self.filter_split = ','
|
273
|
+
self.formats = :json, :html
|
274
|
+
self.number_of_records_in_a_page = 15
|
275
|
+
self.predicate_prefix = '!'
|
276
|
+
self.return_resource = false
|
277
|
+
self.render_enabled = true
|
278
|
+
self.use_permitters = true
|
279
|
+
|
280
|
+
In addition there are some that are controller-only...
|
281
|
+
|
282
|
+
If you don't use the standard controller naming convention, you can define this in the controller:
|
283
|
+
|
284
|
+
self.model_class = YourModel
|
285
|
+
|
286
|
+
If it doesn't handle the other forms well, you can explicitly define the singular/plural names:
|
287
|
+
|
288
|
+
self.model_singular_name = 'your_model'
|
289
|
+
self.model_plural_name = 'your_models'
|
290
|
+
|
291
|
+
These are used for *_url method definitions, to set instance variables like `@foobar` and `@foobars` dynamically, etc.
|
292
|
+
|
293
|
+
Other class attributes are available for setting/overriding, but they are all set by the other class methods defined in the next section.
|
294
|
+
|
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
|
+
#### Routing
|
344
|
+
|
345
|
+
You can just add normal Rails RESTful routes in `config/routes.rb`, e.g. for the Foobar model:
|
346
|
+
|
347
|
+
MyAppName::Application.routes.draw do
|
348
|
+
resources :foobars
|
349
|
+
end
|
350
|
+
|
351
|
+
Supports static, nested, etc. routes also, e.g.:
|
352
|
+
|
353
|
+
MyAppName::Application.routes.draw do
|
354
|
+
namespace :my_service_controller_module do
|
355
|
+
resources :foobars
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
Can pass in params from the path for use in filters, etc. as if they were request parameters:
|
360
|
+
|
361
|
+
MyAppName::Application.routes.draw do
|
362
|
+
namespace :my_service_controller_module do
|
363
|
+
match 'bar/:bar_id/foobars(.:format)' => 'foobars#index'
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
#### Default Filtering by Attribute(s)
|
368
|
+
|
369
|
+
First, declare in the controller:
|
370
|
+
|
371
|
+
can_filter_by :foo_id
|
372
|
+
|
373
|
+
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
|
+
|
375
|
+
http://localhost:3000/foobars?foo_id=1
|
376
|
+
|
377
|
+
`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
|
+
|
379
|
+
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
|
380
|
+
|
381
|
+
`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
|
+
|
383
|
+
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})}
|
384
|
+
|
385
|
+
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
|
+
|
387
|
+
can_filter_by :a_request_param_name, through: [:some_assoc, :some_attr]
|
388
|
+
|
389
|
+
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
|
+
|
391
|
+
can_filter_by :magical_unicorn_name, with_query: ->(t,q,param_value) {q.joins(:magical_unicorns).where(:magical_unicorns=>{name: param_value})}
|
392
|
+
|
393
|
+
or:
|
394
|
+
|
395
|
+
can_filter_by :magical_unicorn_name, through: [:magical_unicorns, :name]
|
396
|
+
|
397
|
+
and you can then use this:
|
398
|
+
|
399
|
+
http://localhost:3000/magical_valleys?magical_unicorn_name=Rainbow
|
400
|
+
|
401
|
+
or if a MagicalUnicorn `has_many :friends` and a MagicalUnicorn's friend has a name attribute:
|
402
|
+
|
403
|
+
can_filter_by :magical_unicorn_friend_name, through: [:magical_unicorns, :friends, :name]
|
404
|
+
|
405
|
+
and use this to get valleys associated with unicorns who in turn have a friend named Oscar:
|
406
|
+
|
407
|
+
http://localhost:3000/magical_valleys?magical_unicorn_friend_name=Oscar
|
408
|
+
|
409
|
+
#### Other Filters by Attribute(s)
|
410
|
+
|
411
|
+
First, declare in the controller:
|
412
|
+
|
413
|
+
can_filter_by :seen_on, using: [:gteq, :eq_any]
|
414
|
+
|
415
|
+
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
|
+
|
417
|
+
http://localhost:3000/foobars?seen_on!gteq=2012-08-08
|
418
|
+
|
419
|
+
Multiple values are separated by `filter_split` (configurable):
|
420
|
+
|
421
|
+
http://localhost:3000/foobars?seen_on!eq_any=2012-08-08,2012-09-09
|
422
|
+
|
423
|
+
#### Supported Functions
|
424
|
+
|
425
|
+
##### Declaring
|
426
|
+
|
427
|
+
`supports_functions` lets you allow the [ARel][arel] functions: `:uniq`, `:skip`, `:take`, and/or `:count`.
|
428
|
+
|
429
|
+
##### Unique (DISTINCT)
|
430
|
+
|
431
|
+
First, declare in the controller:
|
432
|
+
|
433
|
+
supports_functions :uniq
|
434
|
+
|
435
|
+
Now this works:
|
436
|
+
|
437
|
+
http://localhost:3000/foobars?uniq=
|
438
|
+
|
439
|
+
##### Count
|
440
|
+
|
441
|
+
First, declare in the controller:
|
442
|
+
|
443
|
+
supports_functions :count
|
444
|
+
|
445
|
+
Now this works:
|
446
|
+
|
447
|
+
http://localhost:3000/foobars?count=
|
448
|
+
|
449
|
+
##### Paging
|
450
|
+
|
451
|
+
First, declare in the controller:
|
452
|
+
|
453
|
+
supports_functions :page, :page_count
|
454
|
+
|
455
|
+
Now you can get the page count:
|
456
|
+
|
457
|
+
http://localhost:3000/foobars?page_count=
|
458
|
+
|
459
|
+
And access each page of results:
|
460
|
+
|
461
|
+
http://localhost:3000/foobars?page=1
|
462
|
+
http://localhost:3000/foobars?page=2
|
463
|
+
...
|
464
|
+
|
465
|
+
To set page size at application level:
|
466
|
+
|
467
|
+
RestfulJson.number_of_records_in_a_page = 15
|
468
|
+
|
469
|
+
To set page size at controller level:
|
470
|
+
|
471
|
+
self.number_of_records_in_a_page = 15
|
472
|
+
|
473
|
+
##### Skip and Take (OFFSET and LIMIT)
|
474
|
+
|
475
|
+
First, declare in the controller:
|
476
|
+
|
477
|
+
supports_functions :skip, :take
|
478
|
+
|
479
|
+
To skip rows returned, use 'skip'. It is called take, because skip is the [ARel][arel] equivalent of SQL OFFSET:
|
480
|
+
|
481
|
+
http://localhost:3000/foobars?skip=5
|
482
|
+
|
483
|
+
To limit the number of rows returned, use 'take'. It is called take, because take is the [ARel][arel] equivalent of SQL LIMIT:
|
484
|
+
|
485
|
+
http://localhost:3000/foobars.json?take=5
|
486
|
+
|
487
|
+
Combine skip and take for manual completely customized paging, e.g.
|
488
|
+
|
489
|
+
http://localhost:3000/foobars?take=15
|
490
|
+
http://localhost:3000/foobars?skip=15&take=15
|
491
|
+
http://localhost:3000/foobars?skip=30&take=15
|
492
|
+
|
493
|
+
##### Custom Serializers
|
494
|
+
|
495
|
+
If using ActiveModel::Serializers, you can use something other than the `(singular model name)Serializer` via `serialize_action`:
|
496
|
+
|
497
|
+
serialize_action :index, with: ListFoobarSerializer
|
498
|
+
|
499
|
+
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
|
+
|
501
|
+
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
|
+
|
503
|
+
serialize_action :index, :some_custom_action, with: FoosSerializer, for: :array
|
504
|
+
|
505
|
+
Or, you could just use the default serialization, if you want.
|
506
|
+
|
507
|
+
##### Custom Queries
|
508
|
+
|
509
|
+
To filter the list where the status_code attribute is 'green':
|
510
|
+
|
511
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped
|
512
|
+
query_for :index, is: lambda {|t,q| q.where(:status_code => 'green')}
|
513
|
+
|
514
|
+
or use the `->` Ruby 1.9 lambda stab operator (note lack of whitespace between stab and parenthesis):
|
515
|
+
|
516
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped
|
517
|
+
query_for :index, is: is: ->(t,q) {q.where(:status_code => 'green')}
|
518
|
+
|
519
|
+
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:
|
520
|
+
|
521
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped
|
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!
|
532
|
+
|
533
|
+
However `query_for` will create new action methods, so you can easily create custom non-RESTful action methods:
|
534
|
+
|
535
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped
|
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')}
|
538
|
+
|
539
|
+
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.
|
540
|
+
|
541
|
+
query_for :some_action, is: ->(t,q) do
|
542
|
+
if @current_user.admin?
|
543
|
+
Rails.logger.debug("Notice: unfiltered results provided to admin #{@current_user.name}")
|
544
|
+
# just make sure the relation is returned!
|
545
|
+
q
|
546
|
+
else
|
547
|
+
q.where(:access => 'public')
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
Be sure to add a route for that action, e.g. in `config/routes.rb`, e.g. for the Barfoo model:
|
552
|
+
|
553
|
+
MyAppName::Application.routes.draw do
|
554
|
+
resources :barfoos do
|
555
|
+
get 'some_action', :on => :collection
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
### With Rails-api
|
560
|
+
|
561
|
+
If you want to try out [rails-api][rails-api]:
|
562
|
+
|
563
|
+
gem 'rails-api', '~> 0.0.3'
|
564
|
+
|
565
|
+
In `app/controllers/my_service_controller.rb`:
|
566
|
+
|
567
|
+
module MyServiceController
|
568
|
+
extend ActiveSupport::Concern
|
569
|
+
|
570
|
+
included do
|
571
|
+
# Rails-api lets you choose features. You might not need all of these, or may need others.
|
572
|
+
include AbstractController::Translation
|
573
|
+
include ActionController::HttpAuthentication::Basic::ControllerMethods
|
574
|
+
include AbstractController::Layouts
|
575
|
+
include ActionController::MimeResponds
|
576
|
+
include ActionController::Cookies
|
577
|
+
include ActionController::ParamsWrapper
|
578
|
+
|
579
|
+
# use Permitters and AMS
|
580
|
+
include RestfulJson::DefaultController
|
581
|
+
# or comment that last line and uncomment whatever you want to use
|
582
|
+
#include ::ActionController::Serialization # AMS
|
583
|
+
#include ::ActionController::StrongParameters
|
584
|
+
#include ::TwinTurbo::Controller # Permitters which uses Cancan and Strong Parameters
|
585
|
+
#include ::RestfulJson::Controller
|
586
|
+
|
587
|
+
# If you want any additional inline class stuff, it goes here...
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
class FoobarsController < ActionController::API
|
592
|
+
include MyServiceController
|
593
|
+
end
|
594
|
+
|
595
|
+
class BarfoosController < ActionController::API
|
596
|
+
include MyServiceController
|
597
|
+
end
|
598
|
+
|
599
|
+
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
|
+
|
601
|
+
ActiveSupport.on_load(:action_controller) do
|
602
|
+
# without include of ParamsWrapper, will get undefined method `wrap_parameters' for ActionController::API:Class (NoMethodError)
|
603
|
+
include ActionController::ParamsWrapper
|
604
|
+
# in this case it's expecting unwrapped params, but we could maybe use wrap_parameters format: [:json]
|
605
|
+
wrap_parameters format: []
|
606
|
+
end
|
607
|
+
|
608
|
+
# Disable root element in JSON by default.
|
609
|
+
ActiveSupport.on_load(:active_record) do
|
610
|
+
self.include_root_in_json = false
|
611
|
+
end
|
612
|
+
|
613
|
+
### Refactoring and Customing the Default Behavior
|
614
|
+
|
615
|
+
##### Parent/Ancestor Class Definition Not Supported
|
616
|
+
|
617
|
+
Don't subclass and include in the parent, that puts the class attributes into the parent which means they would be shared by the children and bad things can happen.
|
618
|
+
|
619
|
+
Don't do this:
|
620
|
+
|
621
|
+
class ServiceController < ApplicationController
|
622
|
+
include ::ActionController::Serialization
|
623
|
+
include ::ActionController::StrongParameters
|
624
|
+
include ::TwinTurbo::Controller
|
625
|
+
include ::RestfulJson::Controller
|
626
|
+
end
|
627
|
+
|
628
|
+
class FoobarsController < ServiceController
|
629
|
+
end
|
630
|
+
|
631
|
+
class BarfoosController < ServiceController
|
632
|
+
end
|
633
|
+
|
634
|
+
And don't do this:
|
635
|
+
|
636
|
+
class FoobarsController < ApplicationController
|
637
|
+
include RestfulJson::DefaultController
|
638
|
+
end
|
639
|
+
|
640
|
+
class FoobarsController < ServiceController
|
641
|
+
end
|
642
|
+
|
643
|
+
class BarfoosController < ServiceController
|
644
|
+
end
|
645
|
+
|
646
|
+
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
|
+
|
648
|
+
##### Customizing Behavior via Patch
|
649
|
+
|
650
|
+
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
|
+
|
652
|
+
# a horrible Hello World example
|
653
|
+
module RestfulJson
|
654
|
+
module Controller
|
655
|
+
|
656
|
+
# class methods that should be implemented or overriden
|
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
|
681
|
+
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
Now in your controller, if you:
|
686
|
+
|
687
|
+
class FoobarsController < ApplicationController
|
688
|
+
include RestfulJson::DefaultController
|
689
|
+
hello 'world'
|
690
|
+
end
|
691
|
+
|
692
|
+
(Note again: RestfulJson::DefaultController includes RestfulJson::Controller.)
|
693
|
+
|
694
|
+
Now when you call:
|
695
|
+
|
696
|
+
http://localhost:3000/foobars
|
697
|
+
|
698
|
+
You would get the response:
|
699
|
+
|
700
|
+
{'hello': 'world'}
|
701
|
+
|
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.
|
703
|
+
|
704
|
+
### Error Handling
|
705
|
+
|
706
|
+
#### Properly Handling Non-controller-action Errors
|
707
|
+
|
708
|
+
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
|
+
|
710
|
+
Rails 4 has basic error handling defined in the [public_exceptions][public_exceptions] and [show_exceptions][show_exceptions] Rack middleware.
|
711
|
+
|
712
|
+
Rails 3.2.x has support for `config.exceptions_app` which can be defined as the following to simulate Rails 4 exception handling:
|
713
|
+
|
714
|
+
config.exceptions_app = lambda do |env|
|
715
|
+
exception = env["action_dispatch.exception"]
|
716
|
+
status = env["PATH_INFO"][1..-1]
|
717
|
+
request = ActionDispatch::Request.new(env)
|
718
|
+
content_type = request.formats.first
|
719
|
+
body = { :status => status, :error => exception.message }
|
720
|
+
format = content_type && "to_#{content_type.to_sym}"
|
721
|
+
if format && body.respond_to?(format)
|
722
|
+
formatted_body = body.public_send(format)
|
723
|
+
[status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
|
724
|
+
'Content-Length' => body.bytesize.to_s}, [formatted_body]]
|
725
|
+
else
|
726
|
+
found = false
|
727
|
+
path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
|
728
|
+
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
|
729
|
+
|
730
|
+
if found || File.exist?(path)
|
731
|
+
[status, {'Content-Type' => "text/html; charset=#{ActionDispatch::Response.default_charset}",
|
732
|
+
'Content-Length' => body.bytesize.to_s}, [File.read(path)]]
|
733
|
+
else
|
734
|
+
[404, { "X-Cascade" => "pass" }, []]
|
735
|
+
end
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
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
|
+
|
741
|
+
Unfortunately, this doesn't work for Rails 3.1.x. However, in many scenarios there is the chance at a rare situation when the proper format is not returned to the client, even if everything is controlled as much as possible on the server. So, the client really needs to be able to handle such a case of unexpected format with a generic error.
|
742
|
+
|
743
|
+
But, if you can make Rack respond a little better for some errors, that's great.
|
744
|
+
|
745
|
+
#### Controller Error Handling Configuration
|
746
|
+
|
747
|
+
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
|
+
|
749
|
+
There are a few options to customize the rescue and error rendering behavior.
|
750
|
+
|
751
|
+
The `rescue_class` config option specifies what to rescue. Set to StandardError to behave like a normal rescue. Set to nil to just reraise everything rescued (to disable handling).
|
752
|
+
|
753
|
+
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
|
+
|
755
|
+
self.rescue_class = StandardError
|
756
|
+
self.rescue_handlers = [
|
757
|
+
{exception_classes: [ActiveRecord::RecordNotFound], status: :not_found},
|
758
|
+
{status: :internal_server_error}
|
759
|
+
]
|
760
|
+
|
761
|
+
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
|
+
|
763
|
+
self.rescue_class = Exception
|
764
|
+
self.rescue_handlers = [
|
765
|
+
{exception_ancestor_classes: [ActiveRecord::RecordNotFound], status: :not_found, i18n_key: 'api.not_found'.freeze},
|
766
|
+
{i18n_key: 'api.internal_server_error'.freeze}
|
767
|
+
]
|
768
|
+
|
769
|
+
|
770
|
+
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
|
+
|
772
|
+
### Release Notes
|
773
|
+
|
774
|
+
#### restful_json v3.3
|
775
|
+
|
776
|
+
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.
|
777
|
+
|
778
|
+
If you are using Rails 3.1-3.2 and want to use permitters or strong_parameters in all models:
|
779
|
+
|
780
|
+
Make sure you include Strong Parameters:
|
781
|
+
|
782
|
+
gem "strong_parameters"
|
783
|
+
|
784
|
+
Include this in `config/environment.rb`:
|
785
|
+
|
786
|
+
ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)
|
787
|
+
|
788
|
+
If you want to use permitters in all models, you need Cancan:
|
789
|
+
|
790
|
+
Make sure you include Cancan:
|
791
|
+
|
792
|
+
gem "cancan"
|
793
|
+
|
794
|
+
Include this in `config/environment.rb`
|
795
|
+
|
796
|
+
ActiveRecord::Base.send(:include, CanCan::ModelAdditions)
|
797
|
+
|
798
|
+
### Rails Version-specific Eccentricities
|
799
|
+
|
800
|
+
Strong Parameters is included in Rails 4.
|
801
|
+
|
802
|
+
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
|
+
|
804
|
+
### Thanks!
|
805
|
+
|
806
|
+
Without our users, where would we be? Feedback, bug reports, and code/documentation contributions are always welcome!
|
807
|
+
|
808
|
+
### Contributors
|
809
|
+
|
810
|
+
* Gary Weaver (https://github.com/garysweaver)
|
811
|
+
* Tommy Odom (https://github.com/tpodom)
|
812
|
+
|
813
|
+
### License
|
814
|
+
|
815
|
+
Copyright (c) 2013 Gary S. Weaver, released under the [MIT license][lic].
|
816
|
+
|
817
|
+
[employee-training-tracker]: https://github.com/FineLinePrototyping/employee-training-tracker
|
818
|
+
[built_with_angularjs]: http://builtwith.angularjs.org/
|
819
|
+
[permitter]: http://broadcastingadam.com/2012/07/parameter_authorization_in_rails_apis/
|
820
|
+
[cancan]: https://github.com/ryanb/cancan
|
821
|
+
[strong_parameters]: https://github.com/rails/strong_parameters
|
822
|
+
[active_model_serializers]: https://github.com/josevalim/active_model_serializers
|
823
|
+
[authlogic]: https://github.com/binarylogic/authlogic
|
824
|
+
[devise]: https://github.com/plataformatec/devise
|
825
|
+
[arel]: https://github.com/rails/arel
|
826
|
+
[ar]: http://api.rubyonrails.org/classes/ActiveRecord/Relation.html
|
827
|
+
[rails-api]: https://github.com/rails-api/rails-api
|
828
|
+
[railscast320]: http://railscasts.com/episodes/320-jbuilder
|
829
|
+
[public_exceptions]: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
|
830
|
+
[show_exceptions]: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
|
831
|
+
[lic]: http://github.com/rubyservices/restful_json/blob/master/LICENSE
|