Skip to content

Commit d2e8124

Browse files
committed
Merge remote-tracking branch 'cerebris/master' into page-count-meta
2 parents 9d15112 + 174bd2f commit d2e8124

29 files changed

Lines changed: 1935 additions & 1253 deletions

.travis.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@ language: ruby
22
sudo: false
33
env:
44
- "RAILS_VERSION=4.1.0"
5-
- "RAILS_VERSION=4.2.0"
5+
- "RAILS_VERSION=4.2.6"
6+
- "RAILS_VERSION=5.0.0.beta3"
67
rvm:
78
- 2.0
89
- 2.1
9-
- 2.2
10+
- 2.2.4
11+
- 2.3.0
12+
matrix:
13+
exclude:
14+
- rvm: 2.0
15+
env: "RAILS_VERSION=5.0.0.beta3"
16+
- rvm: 2.1
17+
env: "RAILS_VERSION=5.0.0.beta3"

README.md

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
[![Join the chat at https://gitter.im/cerebris/jsonapi-resources](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cerebris/jsonapi-resources?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
44

5-
**_NOTE: There is a Rails 5 branch that is a work in progress. In addition to some changes for Rails 5 support it contains some monkey patches (in `test_helper.rb`) to allow existing tests to pass with Rails 5.0.0.beta1.1_. Things may break with future changes to Rails. If you are using RAILS 5 it is recommended that you use the rails 5 branch.**
6-
75
`JSONAPI::Resources`, or "JR", provides a framework for developing a server that complies with the
86
[JSON API](http://jsonapi.org/) specification.
97

@@ -21,23 +19,29 @@ backed by ActiveRecord models or by custom objects.
2119
* [Usage] (#usage)
2220
* [Resources] (#resources)
2321
* [JSONAPI::Resource] (#jsonapiresource)
22+
* [Context] (#context)
2423
* [Attributes] (#attributes)
2524
* [Primary Key] (#primary-key)
2625
* [Model Name] (#model-name)
26+
* [Model Hints] (#model-hints)
2727
* [Relationships] (#relationships)
2828
* [Filters] (#filters)
2929
* [Pagination] (#pagination)
3030
* [Included relationships (side-loading resources)] (#included-relationships-side-loading-resources)
3131
* [Resource meta] (#resource-meta)
32-
* [Custom Links] (#resource-meta)
32+
* [Custom Links] (#custom-links)
3333
* [Callbacks] (#callbacks)
3434
* [Controllers] (#controllers)
3535
* [Namespaces] (#namespaces)
3636
* [Error Codes] (#error-codes)
3737
* [Handling Exceptions] (#handling-exceptions)
3838
* [Action Callbacks] (#action-callbacks)
3939
* [Serializer] (#serializer)
40+
* [Serializer options] (#serializer-options)
41+
* [Formatting] (#formatting)
42+
* [Key Format] (#key-format)
4043
* [Routing] (#routing)
44+
* [Nested Routes] (#nested-routes)
4145
* [Configuration] (#configuration)
4246
* [Contributing] (#contributing)
4347
* [License] (#license)
@@ -72,8 +76,7 @@ Or install it yourself as:
7276
Resources define the public interface to your API. A resource defines which attributes are exposed, as well as
7377
relationships to other resources.
7478

75-
Resource definitions should by convention be placed in a directory under app named resources, `app/resources`. The class
76-
name should be the single underscored name of the model that backs the resource with `_resource.rb` appended. For example,
79+
Resource definitions should by convention be placed in a directory under app named resources, `app/resources`. The file name should be the single underscored name of the model that backs the resource with `_resource.rb` appended. For example,
7780
a `Contact` model's resource should have a class named `ContactResource` defined in a file named `contact_resource.rb`.
7881

7982
#### JSONAPI::Resource
@@ -155,6 +158,36 @@ In the above example vehicles are immutable. A call to `/vehicles` or `/vehicles
155158
of either `car` or `boat`. But calls to PUT or POST a `car` must be made to `/cars`. The rails models backing the above
156159
code use Single Table Inheritance.
157160

161+
#### Context
162+
163+
Sometimes you will want to access things such as the current logged in user (and other state only available within your controllers) from within your resource classes. To make this state available to a resource class you need to put it into the context hash - this can be done via a `context` method on one of your controllers or across all controllers using ApplicationController.
164+
165+
For example:
166+
167+
```ruby
168+
class ApplicationController < JSONAPI::ResourceController
169+
def context
170+
{current_user: current_user}
171+
end
172+
end
173+
174+
# Specific resource controllers derive from ApplicationController
175+
# and share its context
176+
class PeopleController < ApplicationController
177+
178+
end
179+
180+
# Assuming you don't permit user_id (so the client won't assign a wrong user to own the object)
181+
# you can ensure the current user is assigned the record by using the controller's context hash.
182+
class PeopleResource < JSONAPI::Resource
183+
before_save do
184+
@model.user_id = context[:current_user].id if @model.new_record?
185+
end
186+
end
187+
```
188+
189+
You can put things that affect serialization and resource configuration into the context.
190+
158191
#### Attributes
159192

160193
Any of a resource's attributes that are accessible must be explicitly declared. Single attributes can be declared using
@@ -188,6 +221,18 @@ class ContactResource < JSONAPI::Resource
188221
end
189222
```
190223

224+
##### Attribute Delegation
225+
226+
Normally resource attributes map to an attribute on the model of the same name. Using the `delegate` option allows a resource
227+
attribute to map to a differently named model attribute. For example:
228+
229+
```ruby
230+
class ContactResource < JSONAPI::Resource
231+
attribute :name_first, delegate: :first_name
232+
attribute :name_last, delegate: :last_name
233+
end
234+
```
235+
191236
##### Fetchable Attributes
192237

193238
By default all attributes are assumed to be fetchable. The list of fetchable attributes can be filtered by overriding
@@ -261,6 +306,32 @@ class PostResource < JSONAPI::Resource
261306
end
262307
```
263308

309+
JR also supports sorting primary resources by fields on relationships.
310+
311+
Here's an example of sorting books by the author name:
312+
313+
```ruby
314+
class Book < ActiveRecord::Base
315+
belongs_to :author
316+
end
317+
318+
class Author < ActiveRecord::Base
319+
has_many :books
320+
end
321+
322+
class BookResource < JSONAPI::Resource
323+
attributes :title, :body
324+
325+
def self.sortable_fields(context)
326+
super(context) << :"author.name"
327+
end
328+
end
329+
```
330+
The request will look something like:
331+
```
332+
GET /books?include=author&sort=author.name
333+
```
334+
264335
##### Attribute Formatting
265336

266337
Attributes can have a `Format`. By default all attributes use the default formatter. If an attribute has the `format`
@@ -673,7 +744,7 @@ default any other error that you raise will return a `500` status code
673744
for a general internal server error.
674745

675746
To return useful error codes that represent application errors you
676-
should set the `exception_class_whitelist` config varible, and then you
747+
should set the `exception_class_whitelist` config variable, and then you
677748
should use the Rails `rescue_from` macro to render a status code.
678749

679750
For example, this config setting allows the `NotAuthorizedError` to bubble up out of
@@ -927,7 +998,7 @@ class BookResource < JSONAPI::Resource
927998
def meta(options)
928999
{
9291000
copyright: 'API Copyright 2015 - XYZ Corp.',
930-
computed_copyright: options[:serialization_options][:copyright]
1001+
computed_copyright: options[:serialization_options][:copyright],
9311002
last_updated_at: _model.updated_at
9321003
}
9331004
end
@@ -956,7 +1027,7 @@ class CityCouncilMeeting < JSONAPI::Resource
9561027
attribute :title, :location, :approved
9571028

9581029
def custom_links(options)
959-
{ minutes: options[:serialzer].link_builder.self_link(self) + "/minutes" }
1030+
{ minutes: options[:serializer].link_builder.self_link(self) + "/minutes" }
9601031
end
9611032
end
9621033
```
@@ -991,7 +1062,7 @@ class CityCouncilMeeting < JSONAPI::Resource
9911062
def custom_links(options)
9921063
extra_links = {}
9931064
if approved?
994-
extra_links[:minutes] = options[:serialzer].link_builder.self_link(self) + "/minutes"
1065+
extra_links[:minutes] = options[:serializer].link_builder.self_link(self) + "/minutes"
9951066
end
9961067
extra_links
9971068
end
@@ -1135,26 +1206,6 @@ A jsonapi-controller generator is avaliable
11351206
rails generate jsonapi:controller contact
11361207
```
11371208
1138-
###### Context
1139-
1140-
The context that's used for serialization and resource configuration is set by the controller's `context` method.
1141-
1142-
For example:
1143-
1144-
```ruby
1145-
class ApplicationController < JSONAPI::ResourceController
1146-
def context
1147-
{current_user: current_user}
1148-
end
1149-
end
1150-
1151-
# Specific resource controllers derive from ApplicationController
1152-
# and share its context
1153-
class PeopleController < ApplicationController
1154-
1155-
end
1156-
```
1157-
11581209
###### Serialization Options
11591210
11601211
Additional options can be passed to the serializer using the `serialization_options` method.
@@ -1854,4 +1905,4 @@ end
18541905

18551906
## License
18561907

1857-
Copyright 2014 Cerebris Corporation. MIT License (see LICENSE for details).
1908+
Copyright 2014-2016 Cerebris Corporation. MIT License (see LICENSE for details).

Rakefile

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
#!/usr/bin/env rake
22
require 'bundler/gem_tasks'
33
require 'rake/testtask'
4-
require './test/test_helper.rb'
54

6-
TestApp.load_tasks
5+
Rake::TestTask.new do |t|
6+
t.verbose = true
7+
t.warning = false
8+
t.test_files = FileList['test/**/*_test.rb']
9+
end
710

811
task default: :test
912

10-
desc 'Run tests in isolated processes'
13+
desc 'Run benchmarks'
1114
namespace :test do
12-
task :isolated do
13-
Dir[test_task.pattern].each do |file|
14-
cmd = ['ruby']
15-
test_task.libs.each { |l| cmd << '-I' << l }
16-
cmd << file
17-
sh cmd.join(' ')
18-
end
15+
Rake::TestTask.new(:benchmark) do |t|
16+
t.pattern = 'test/benchmark/*_benchmark.rb'
1917
end
2018
end

jsonapi-resources.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@ Gem::Specification.new do |spec|
2525
spec.add_development_dependency 'minitest-spec-rails'
2626
spec.add_development_dependency 'simplecov'
2727
spec.add_development_dependency 'pry'
28+
spec.add_development_dependency 'concurrent-ruby-ext'
2829
spec.add_dependency 'rails', '>= 4.0'
30+
spec.add_dependency 'concurrent-ruby'
2931
end

lib/jsonapi-resources.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require 'jsonapi/naive_cache'
12
require 'jsonapi/resource'
23
require 'jsonapi/response_document'
34
require 'jsonapi/acts_as_resource_controller'

lib/jsonapi/acts_as_resource_controller.rb

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ def process_request
6666

6767
rescue => e
6868
handle_exceptions(e)
69-
ensure
70-
if response.body.size > 0
71-
response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
72-
end
7369
end
7470

7571
# set the operations processor in the configuration or override this to use another operations processor
@@ -117,7 +113,7 @@ def serialization_options
117113
# JSONAPI.configuration.route = :camelized_route
118114
#
119115
# Override if you want to set a per controller key format.
120-
# Must return a class derived from KeyFormatter.
116+
# Must return an instance of a class derived from KeyFormatter.
121117
def key_formatter
122118
JSONAPI.configuration.key_formatter
123119
end
@@ -152,7 +148,18 @@ def render_errors(errors)
152148

153149
def render_results(operation_results)
154150
response_doc = create_response_document(operation_results)
155-
render status: response_doc.status, json: response_doc.contents
151+
152+
render_options = {
153+
status: response_doc.status,
154+
json: response_doc.contents,
155+
content_type: JSONAPI::MEDIA_TYPE
156+
}
157+
158+
render_options[:location] = response_doc.contents[:data]["links"][:self] if (
159+
response_doc.status == :created && response_doc.contents[:data].class != Array
160+
)
161+
162+
render(render_options)
156163
end
157164

158165
def create_response_document(operation_results)
@@ -179,7 +186,7 @@ def handle_exceptions(e)
179186
when JSONAPI::Exceptions::Error
180187
render_errors(e.errors)
181188
else
182-
if JSONAPI.configuration.exception_class_whitelist.any? { |k| e.class.ancestors.include?(k) }
189+
if JSONAPI.configuration.exception_class_whitelisted?(e)
183190
fail e
184191
else
185192
internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)

0 commit comments

Comments
 (0)