jsonapi-resources 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e5e235b0488716337c8c547c7a97bdbd1df7c05a
4
+ data.tar.gz: af645d11d41b982eb57a1c7a340be2692a9633a9
5
+ SHA512:
6
+ metadata.gz: 4365d7b76273359e386534a52a9ba7a40608d674c4e643815f564588afa8480f16b61cad7337e78dc13162872ae84e6b7a6fd9ca1a0a4d262f22877633fab008
7
+ data.tar.gz: 8f6bc0f211760c55df35515e61833bb3413ace9c093b9f56b5ec45b875476043110a2acc49b42446efb588ab9b63cb7958bece1e688b26bcb044f89614aad8e6
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .ruby-version
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ coverage
20
+ test/log
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ platforms :ruby do
6
+ # sqlite3 1.3.9 does not work with rubinius 2.2.5:
7
+ # https://github.com/sparklemotion/sqlite3-ruby/issues/122
8
+ gem 'sqlite3', '1.3.8'
9
+ end
10
+
11
+ platforms :jruby do
12
+ gem 'activerecord-jdbcsqlite3-adapter'
13
+ end
14
+
15
+ version = ENV['RAILS_VERSION'] || '4.0.4'
16
+ rails = case version
17
+ when 'master'
18
+ {:github => 'rails/rails'}
19
+ else
20
+ "~> #{version}"
21
+ end
22
+ gem 'rails', rails
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Larry Gebhardt
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,451 @@
1
+ # JSONAPI::Resources
2
+
3
+ JSONAPI::Resources, or "JR", provides a framework for developing a server that complies with the [JSON API](http://jsonapi.org/) specification.
4
+
5
+ Like JSON API itself, JR's design is focused on the resources served by an API. JR needs little more than a definition of your resources, including their attributes and relationships, to make your server compliant with JSON API.
6
+
7
+ JR is designed to work with Rails, and provides custom routes, controllers, and serializers. JR's resources may be backed by ActiveRecord models or by custom objects.
8
+
9
+ ## Demo App
10
+
11
+ We have a simple demo app, called [Peeps](https://github.com/cerebris/peeps), available to show how JR is used.
12
+
13
+ ## Installation
14
+
15
+ Add JR to your application's `Gemfile`:
16
+
17
+ gem 'jsonapi-resources'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install jsonapi-resources
26
+
27
+ ## Usage
28
+
29
+ ### Resources
30
+
31
+ Resources define the public interface to your API. A resource defines which attributes are exposed, as well as relationships to other resources.
32
+
33
+ Resource definitions should by convention be placed in a directory under app named resources, `app/resources`. The class name should be the single underscored name of the model that backs the resource with `_resource.rb` appended. For example, a `Contact` model's resource should have a class named `ContactResource` defined in a file named `contact_resource.rb`.
34
+
35
+ #### JSONAPI::Resource
36
+
37
+ Resources must be derived from `JSONAPI::Resource`, or a class that is itself derived from `JSONAPI::Resource`.
38
+
39
+ For example:
40
+
41
+ ```
42
+ require 'jsonapi/resource'
43
+
44
+ class ContactResource < JSONAPI::Resource
45
+ end
46
+ ```
47
+
48
+ #### Attributes
49
+
50
+ Any of a resource's attributes that are accessible must be explicitly declared. Single attributes can be declared using the `attribute` method, and multiple attributes can be declared with the `attributes` method on the resource class.
51
+
52
+ For example:
53
+
54
+ ```
55
+ require 'jsonapi/resource'
56
+
57
+ class ContactResource < JSONAPI::Resource
58
+ attribute :id
59
+ attribute :name_first
60
+ attributes :name_last, :email, :twitter
61
+ end
62
+ ```
63
+
64
+ This resource has 5 attributes: `:id`, `:name_first`, `:name_last`, `:email`, `:twitter`. By default these attributes must exist on the model that is handled by the resource.
65
+
66
+ A resource object wraps a Ruby object, usually an ActiveModel record, which is available as the `@object` variable. This allows a resource's methods to access the underlying object.
67
+
68
+ For example, a computed attribute for `full_name` could be defined as such:
69
+
70
+ ```
71
+ require 'jsonapi/resource'
72
+
73
+ class ContactResource < JSONAPI::Resource
74
+ attributes :id, :name_first, :name_last, :email, :twitter
75
+ attribute :full_name
76
+
77
+ def full_name
78
+ "#{@object.name_first}, #{@object.name_last}"
79
+ end
80
+ end
81
+ ```
82
+
83
+ ##### Fetchable Attributes
84
+
85
+ By default all attributes are assumed to be fetchable. The list of fetchable attributes can be filtered by overriding the `fetchable` method.
86
+
87
+ Here's an example that prevents guest users from seeing the `email` field:
88
+
89
+ ```
90
+ class AuthorResource < JSONAPI::Resource
91
+ attributes :id, :name, :email
92
+ model_name 'Person'
93
+ has_many :posts
94
+
95
+ def fetchable(keys, context)
96
+ if (context.current_user.guest)
97
+ super(keys - [:email])
98
+ else
99
+ super(keys)
100
+ end
101
+ end
102
+ end
103
+ ```
104
+
105
+ Context flows through from the controller and can be used to control the attributes based on the current user (or other value)).
106
+
107
+ ##### Creatable and Updateable Attributes
108
+
109
+ By default all attributes are assumed to be updateble and creatable. To prevent some attributes from being accepted by the `update` or `create` methods, override the `self.updateable` and `self.creatable` methods on a resource.
110
+
111
+ This example prevents `full_name` from being set:
112
+
113
+ ```
114
+ require 'jsonapi/resource'
115
+
116
+ class ContactResource < JSONAPI::Resource
117
+ attributes :id, :name_first, :name_last, :full_name
118
+
119
+ def full_name
120
+ "#{@object.name_first}, #{@object.name_last}"
121
+ end
122
+
123
+ def self.updateable(keys, context = nil)
124
+ super(keys - [:full_name])
125
+ end
126
+
127
+ def self.createable(keys, context = nil)
128
+ super(keys - [:full_name])
129
+ end
130
+ end
131
+ ```
132
+
133
+ The `context` is not used by the `ResourceController`, but may be used if you override the controller methods.
134
+
135
+ #### Key
136
+
137
+ The primary key of the resource defaults to `id`, which can be changed using the `key` method.
138
+
139
+ ```
140
+ class CurrencyResource < JSONAPI::Resource
141
+ key :code
142
+ attributes :code, :name
143
+
144
+ has_many :expense_entries
145
+ end
146
+
147
+ ```
148
+
149
+ #### Model Name
150
+
151
+ The name of the underlying model is inferred from the Resource name. It can be overridden by use of the `model_name` method. For example:
152
+
153
+ ```
154
+ class AuthorResource < JSONAPI::Resource
155
+ attributes :id, :name
156
+ model_name 'Person'
157
+ has_many :posts
158
+ end
159
+ ```
160
+
161
+ #### Associations
162
+
163
+ Related resources need to be specified in the resource. These are declared with the `has_one` and the `has_many` methods.
164
+
165
+ Here's a simple example where a post has a single author and an author can have many posts:
166
+
167
+ ```
168
+ class PostResource < JSONAPI::Resource
169
+ attribute :id, :title, :body
170
+
171
+ has_one :author
172
+ end
173
+ ```
174
+
175
+ And the corresponding author:
176
+
177
+ ```
178
+ class AuthorResource < JSONAPI::Resource
179
+ attribute :id, :name
180
+
181
+ has_many :posts
182
+ end
183
+ ```
184
+
185
+ ##### Options
186
+
187
+ The association methods support the following options:
188
+ * `class_name` - a string specifying the underlying class for the related resource
189
+ * `primary_key` - the primary key to the related resource, if different than `id`
190
+ * `key` - the key in the resource that identifies the related resource, if different than `<resource_name>_id`
191
+ * `treat_as_set` - allows the entire set of related records to be replaced in one operation. Defaults to false if not set.
192
+
193
+ Examples:
194
+
195
+ ```
196
+ class CommentResource < JSONAPI::Resource
197
+ attributes :id, :body
198
+ has_one :post
199
+ has_one :author, class_name: 'Person'
200
+ has_many :tags, treat_as_set: true
201
+ end
202
+ ```
203
+
204
+ ```
205
+ class ExpenseEntryResource < JSONAPI::Resource
206
+ attributes :id, :cost, :transaction_date
207
+
208
+ has_one :currency, class_name: 'Currency', key: 'currency_code'
209
+ has_one :employee
210
+ end
211
+ ```
212
+
213
+ #### Filters
214
+
215
+ Filters for locating objects of the resource type are specified in the resource definition. Single filters can be declared using the `filter` method, and multiple filters can be declared with the `filters` method on the
216
+ resource class.
217
+
218
+ For example:
219
+
220
+ ```
221
+ require 'jsonapi/resource'
222
+
223
+ class ContactResource < JSONAPI::Resource
224
+ attributes :id, :name_first, :name_last, :email, :twitter
225
+
226
+ filter :id
227
+ filters :name_first, :name_last
228
+ end
229
+ ```
230
+
231
+ ##### Finders
232
+
233
+ Basic finding by filters is supported by resources. However if you have more complex requirements for finding you can override the `find` and `find_by_key` methods on the resource.
234
+
235
+ Here's an example that defers the `find` operation to a `current_user` set on the `context`:
236
+
237
+ ```
238
+ class AuthorResource < JSONAPI::Resource
239
+ attributes :id, :name
240
+ model_name 'Person'
241
+ has_many :posts
242
+
243
+ filter :name
244
+
245
+ def self.find(attrs, context = nil)
246
+ authors = context.current_user.find_authors(attrs)
247
+
248
+ return authors.map do |author|
249
+ self.new(author)
250
+ end
251
+ end
252
+ end
253
+ ```
254
+
255
+ ### Controllers
256
+
257
+ JSONAPI::Resources provides a class, `ResourceController`, that can be used as the base class for your controllers. `ResourceController` supports `index`, `show`, `create`, `update`, and `destroy` methods. Just deriving your controller from `ResourceController` will give you a fully functional controller.
258
+
259
+ For example:
260
+
261
+ ```
262
+ class PeopleController < JSONAPI::ResourceController
263
+
264
+ end
265
+ ```
266
+
267
+ Of course you are free to extend this as needed and override action handlers or other methods.
268
+
269
+ The context that's used for serialization and resource configuration is set by the controller's `context` method.
270
+
271
+ For example:
272
+
273
+ ```
274
+ class ApplicationController < JSONAPI::ResourceController
275
+ def context
276
+ {current_user: current_user}
277
+ end
278
+ end
279
+
280
+ # Specific resource controllers derive from ApplicationController
281
+ # and share its context
282
+ class PeopleController < ApplicationController
283
+
284
+ end
285
+ ```
286
+
287
+ #### Error codes
288
+
289
+ Error codes are provided for each error object returned, based on the error. These errors are:
290
+
291
+ ```
292
+ module JSONAPI
293
+ VALIDATION_ERROR = 100
294
+ INVALID_RESOURCE = 101
295
+ FILTER_NOT_ALLOWED = 102
296
+ INVALID_FIELD_VALUE = 103
297
+ INVALID_FIELD = 104
298
+ PARAM_NOT_ALLOWED = 105
299
+ PARAM_MISSING = 106
300
+ INVALID_FILTER_VALUE = 107
301
+ COUNT_MISMATCH = 108
302
+ KEY_ORDER_MISMATCH = 109
303
+ KEY_NOT_INCLUDED_IN_URL = 110
304
+
305
+ RECORD_NOT_FOUND = 404
306
+ LOCKED = 423
307
+ end
308
+ ```
309
+
310
+ These codes can be customized in your app by creating an initializer to override any or all of the codes.
311
+
312
+ ### Serializer
313
+
314
+ The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` has a `serialize` method that takes a resource instance to serialize. For example:
315
+
316
+ ```
317
+ post = Post.find(1)
318
+ JSONAPI::ResourceSerializer.new.serialize(PostResource.new(post))
319
+ ```
320
+
321
+ This returns results like this:
322
+
323
+ ```
324
+ {
325
+ posts: [{
326
+ id: 1,
327
+ title: 'New post',
328
+ body: 'A body!!!',
329
+ links: {
330
+ section: nil,
331
+ author: 1,
332
+ tags: [1,2,3],
333
+ comments: [1,2]
334
+ }
335
+ }]
336
+ }
337
+ ```
338
+
339
+ #### Serialize method options
340
+
341
+ The serialize method also takes some optional parameters:
342
+
343
+ ##### `include`
344
+
345
+ An array of resources. Nested resources can be specified with dot notation.
346
+
347
+ *Purpose*: determines which objects will be side loaded with the source objects in a linked section
348
+
349
+ *Example*: ```include: ['comments','author','comments.tags','author.posts']```
350
+
351
+ ##### `fields`
352
+
353
+ A hash of resource types and arrays of fields for each resource type.
354
+
355
+ *Purpose*: determines which fields are serialized for a resource type. This encompasses both attributes and association ids in the links section for a resource. Fields are global for a resource type.
356
+
357
+ *Example*: ```fields: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}```
358
+
359
+ ```
360
+ post = Post.find(1)
361
+ JSONAPI::ResourceSerializer.new.serialize(PostResource.new(post),
362
+ include: ['comments','author','comments.tags','author.posts'],
363
+ fields: {
364
+ people: [:id, :email, :comments],
365
+ posts: [:id, :title, :author],
366
+ tags: [:name],
367
+ comments: [:id, :body, :post]})
368
+ ```
369
+
370
+ ##### `context`
371
+
372
+ Context data can be provided to the serializer, which passes it to each resource as it is inspected.
373
+
374
+ #### Routing
375
+
376
+ JR has a couple of helper methods available to assist you with setting up routes.
377
+
378
+ ##### `jsonapi_resources`
379
+
380
+ Like `resources` in ActionDispatch provides a resourceful route provides a mapping between HTTP verbs and URLs and
381
+ controller actions. This will also setup mappings for relationship URLs for a resource's associations. For example
382
+
383
+ ```
384
+ require 'jsonapi/routing_ext'
385
+
386
+ Peeps::Application.routes.draw do
387
+ jsonapi_resources :contacts
388
+ jsonapi_resources :phone_numbers
389
+ end
390
+ ```
391
+
392
+ gives the following routes
393
+
394
+ ```
395
+ Prefix Verb URI Pattern Controller#Action
396
+ contact_links_phone_numbers GET /contacts/:contact_id/links/phone_numbers(.:format) contacts#show_association {:association=>"phone_numbers"}
397
+ POST /contacts/:contact_id/links/phone_numbers(.:format) contacts#create_association {:association=>"phone_numbers"}
398
+ DELETE /contacts/:contact_id/links/phone_numbers/:keys(.:format) contacts#destroy_association {:association=>"phone_numbers"}
399
+ contacts GET /contacts(.:format) contacts#index
400
+ POST /contacts(.:format) contacts#create
401
+ new_contact GET /contacts/new(.:format) contacts#new
402
+ edit_contact GET /contacts/:id/edit(.:format) contacts#edit
403
+ contact GET /contacts/:id(.:format) contacts#show
404
+ PATCH /contacts/:id(.:format) contacts#update
405
+ PUT /contacts/:id(.:format) contacts#update
406
+ DELETE /contacts/:id(.:format) contacts#destroy
407
+ phone_number_links_contact GET /phone_numbers/:phone_number_id/links/contact(.:format) phone_numbers#show_association {:association=>"contact"}
408
+ POST /phone_numbers/:phone_number_id/links/contact(.:format) phone_numbers#create_association {:association=>"contact"}
409
+ DELETE /phone_numbers/:phone_number_id/links/contact(.:format) phone_numbers#destroy_association {:association=>"contact"}
410
+ phone_numbers GET /phone_numbers(.:format) phone_numbers#index
411
+ POST /phone_numbers(.:format) phone_numbers#create
412
+ new_phone_number GET /phone_numbers/new(.:format) phone_numbers#new
413
+ edit_phone_number GET /phone_numbers/:id/edit(.:format) phone_numbers#edit
414
+ phone_number GET /phone_numbers/:id(.:format) phone_numbers#show
415
+ PATCH /phone_numbers/:id(.:format) phone_numbers#update
416
+ PUT /phone_numbers/:id(.:format) phone_numbers#update
417
+ DELETE /phone_numbers/:id(.:format) phone_numbers#destroy
418
+ ```
419
+
420
+ ##### `jsonapi_resource`
421
+
422
+ Like `jsonapi_resources`, but for resources you lookup without an id.
423
+
424
+ ##### `jsonapi_links`
425
+
426
+ You can control the relationship routes by passing a block into `jsonapi_resources` or `jsonapi_resource`. An empty block
427
+ will not create any relationship routes.
428
+
429
+ You can add relationship routes in with `jsonapi_links`, for example:
430
+
431
+ ```
432
+ jsonapi_resources :posts, except: [:destroy] do
433
+ jsonapi_link :author, except: [:destroy]
434
+ jsonapi_links :tags, only: [:show, :create]
435
+ end
436
+
437
+ ```
438
+
439
+ This will create relationship routes for author (show and create, but not destroy) and for tags (again show and create, but not destroy).
440
+
441
+ ## Contributing
442
+
443
+ 1. Fork it ( http://github.com/cerebris/jsonapi-resources/fork )
444
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
445
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
446
+ 4. Push to the branch (`git push origin my-new-feature`)
447
+ 5. Create a new Pull Request
448
+
449
+ ## License
450
+
451
+ Copyright 2014 Cerebris Corporation. MIT License (see LICENSE for details).