Skip to content

Commit b8e2aff

Browse files
committed
Merge branch 'master' into id_respect_creatable
2 parents d579a9f + 4015be1 commit b8e2aff

31 files changed

Lines changed: 1414 additions & 1348 deletions

.travis.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ sudo: false
33
env:
44
- "RAILS_VERSION=4.1.0"
55
- "RAILS_VERSION=4.2.6"
6-
- "RAILS_VERSION=5.0.0.beta3"
6+
- "RAILS_VERSION=5.0.0.rc1"
77
rvm:
8-
- 2.0
98
- 2.1
109
- 2.2.4
1110
- 2.3.0
1211
matrix:
1312
exclude:
1413
- rvm: 2.0
15-
env: "RAILS_VERSION=5.0.0.beta3"
14+
env: "RAILS_VERSION=5.0.0.rc1"
1615
- rvm: 2.1
17-
env: "RAILS_VERSION=5.0.0.beta3"
16+
env: "RAILS_VERSION=5.0.0.rc1"

README.md

Lines changed: 80 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ backed by ActiveRecord models or by custom objects.
3636
* [Error Codes] (#error-codes)
3737
* [Handling Exceptions] (#handling-exceptions)
3838
* [Action Callbacks] (#action-callbacks)
39+
* [Operation Processors] (#operation-processors)
3940
* [Serializer] (#serializer)
4041
* [Serializer options] (#serializer-options)
4142
* [Formatting] (#formatting)
@@ -1117,68 +1118,31 @@ Callbacks can be defined for the following `JSONAPI::Resource` events:
11171118
- `:remove_to_one_link`
11181119
- `:replace_fields`
11191120
1120-
##### `JSONAPI::OperationsProcessor` Callbacks
1121+
##### `JSONAPI::Processor` Callbacks
11211122
1122-
Callbacks can also be defined for `JSONAPI::OperationsProcessor` events:
1123-
- `:operations`: The set of operations.
1123+
Callbacks can also be defined for `JSONAPI::Processor` events:
11241124
- `:operation`: Any individual operation.
1125-
- `:find_operation`: A `find_operation`.
1126-
- `:show_operation`: A `show_operation`.
1127-
- `:show_relationship_operation`: A `show_relationship_operation`.
1128-
- `:show_related_resource_operation`: A `show_related_resource_operation`.
1129-
- `:show_related_resources_operation`: A `show_related_resources_operation`.
1130-
- `:create_resource_operation`: A `create_resource_operation`.
1131-
- `:remove_resource_operation`: A `remove_resource_operation`.
1132-
- `:replace_fields_operation`: A `replace_fields_operation`.
1133-
- `:replace_to_one_relationship_operation`: A `replace_to_one_relationship_operation`.
1134-
- `:create_to_many_relationship_operation`: A `create_to_many_relationship_operation`.
1135-
- `:replace_to_many_relationship_operation`: A `replace_to_many_relationship_operation`.
1136-
- `:remove_to_many_relationship_operation`: A `remove_to_many_relationship_operation`.
1137-
- `:remove_to_one_relationship_operation`: A `remove_to_one_relationship_operation`.
1138-
1139-
The operation callbacks have access to two meta data hashes, `@operations_meta` and `@operation_meta`, two links hashes,
1140-
`@operations_links` and `@operation_links`, the full list of `@operations`, each individual `@operation` and the
1141-
`@result` variables.
1142-
1143-
##### Custom `OperationsProcessor` Example to Return total_count in Meta
1144-
1145-
Note: this can also be accomplished with the `top_level_meta_include_record_count` option, and in most cases that will
1146-
be the better option.
1147-
1148-
To return the total record count of a find operation in the meta data of a find operation you can create a custom
1149-
OperationsProcessor. For example:
1150-
1151-
```ruby
1152-
# lib/jsonapi/counting_active_record_operations_processor.rb
1153-
class CountingActiveRecordOperationsProcessor < ActiveRecordOperationsProcessor
1154-
after_find_operation do
1155-
@operation_meta[:total_records] = @operation.record_count
1156-
end
1157-
end
1158-
```
1159-
1160-
Set the configuration option `operations_processor` to use the new `CountingActiveRecordOperationsProcessor` by
1161-
specifying the snake cased name of the class (without the `OperationsProcessor`).
1162-
1163-
```ruby
1164-
require 'jsonapi/counting_active_record_operations_processor'
1165-
1166-
JSONAPI.configure do |config|
1167-
config.operations_processor = :counting_active_record
1168-
end
1169-
```
1170-
1171-
To use a specific `OperationsProcessor` in a `ResourceController`, override the `create_operations_processor` method:
1172-
1173-
```ruby
1174-
def create_operations_processor
1175-
CountingActiveRecordOperationsProcessor.new
1176-
end
1177-
```
1178-
1179-
The callback code will be called after each find. It will use the same options as the find operation, without the
1180-
pagination, to collect the record count. This is stored in the `operation_meta`, which will be returned in the top level
1181-
meta element.
1125+
- `:find`: A `find` operation is being processed.
1126+
- `:show`: A `show` operation is being processed.
1127+
- `:show_relationship`: A `show_relationship` operation is being processed.
1128+
- `:show_related_resource`: A `show_related_resource` operation is being processed.
1129+
- `:show_related_resources`: A `show_related_resources` operation is being processed.
1130+
- `:create_resource`: A `create_resource` operation is being processed.
1131+
- `:remove_resource`: A `remove_resource` operation is being processed.
1132+
- `:replace_fields`: A `replace_fields` operation is being processed.
1133+
- `:replace_to_one_relationship`: A `replace_to_one_relationship` operation is being processed.
1134+
- `:create_to_many_relationship`: A `create_to_many_relationship` operation is being processed.
1135+
- `:replace_to_many_relationship`: A `replace_to_many_relationship` operation is being processed.
1136+
- `:remove_to_many_relationship`: A `remove_to_many_relationship` operation is being processed.
1137+
- `:remove_to_one_relationship`: A `remove_to_one_relationship` operation is being processed.
1138+
1139+
See [Operation Processors] (#operation-processors) for details on using OperationPprocessors
1140+
1141+
##### `JSONAPI::OperationsProcessor` Callbacks (a removed feature)
1142+
1143+
Note: The `JSONAPI::OperationsProcessor` has been removed and replaced with the `JSONAPI::OperationDispatcher`
1144+
and `Processor` classes per resource. The callbacks have been renamed and moved to the
1145+
`Processor`s, with the exception of the `operations` callback which is now on the controller.
11821146
11831147
### Controllers
11841148
@@ -1202,6 +1166,7 @@ end
12021166
Of course you are free to extend this as needed and override action handlers or other methods.
12031167
12041168
A jsonapi-controller generator is avaliable
1169+
12051170
```
12061171
rails generate jsonapi:controller contact
12071172
```
@@ -1355,6 +1320,7 @@ module JSONAPI
13551320
SAVE_FAILED = '121'
13561321
FORBIDDEN = '403'
13571322
RECORD_NOT_FOUND = '404'
1323+
NOT_ACCEPTABLE = '406'
13581324
UNSUPPORTED_MEDIA_TYPE = '415'
13591325
LOCKED = '423'
13601326
end
@@ -1416,6 +1382,56 @@ class UsersController < JSONAPI::ResourceController
14161382
end
14171383
```
14181384

1385+
### Operation Processors
1386+
1387+
Operation Processors are called to perform the operation(s) that make up a request. The controller (through the `OperationDispatcher`), creates an `OperatorProcessor` to handle each operation. The processor is created based on the resource name, including the namespace. If a processor does not exist for a resource (namespace matters) the default operation processor is used instead. The default processor can be changed by a configuration setting.
1388+
1389+
Defining a custom `Processor` allows for custom callback handling of each operation type for each resource type. For example:
1390+
1391+
```ruby
1392+
class Api::V4::BookProcessor < JSONAPI::Processor
1393+
after_find do
1394+
unless @result.is_a?(JSONAPI::ErrorsOperationResult)
1395+
@result.meta[:total_records_found] = @result.record_count
1396+
end
1397+
end
1398+
end
1399+
```
1400+
1401+
This simple example uses a callback to update the result's meta property with the total count of records (a redundant
1402+
feature only for example purposes), if there wasn't an error in the operation. It is also possible to override the
1403+
`find` method as well if a different behavior is needed, for example:
1404+
1405+
```ruby
1406+
class Api::V4::BookProcessor < JSONAPI::Processor
1407+
def find
1408+
filters = params[:filters]
1409+
include_directives = params[:include_directives]
1410+
sort_criteria = params.fetch(:sort_criteria, [])
1411+
paginator = params[:paginator]
1412+
1413+
verified_filters = resource_klass.verify_filters(filters, context)
1414+
resource_records = resource_klass.find(verified_filters,
1415+
context: context,
1416+
include_directives: include_directives,
1417+
sort_criteria: sort_criteria,
1418+
paginator: paginator)
1419+
1420+
page_options = {}
1421+
# Overriding the default record count logic to always include it in the meta
1422+
#if (JSONAPI.configuration.top_level_meta_include_record_count ||
1423+
# (paginator && paginator.class.requires_record_count))
1424+
page_options[:record_count] = resource_klass.find_count(verified_filters,
1425+
context: context,
1426+
include_directives: include_directives)
1427+
#end
1428+
end
1429+
```
1430+
1431+
Note: The authors of this gem expect the most common uses cases to be handled using the callbacks. It is likely that the
1432+
internal functionality of the operation processing methods will change, at least for several revisions. Effort will be
1433+
made to call this out in release notes. You have been warned.
1434+
14191435
### Serializer
14201436

14211437
The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` must be
@@ -1836,8 +1852,7 @@ JR has a few configuration options. Some have already been mentioned above. To s
18361852
initializer and add the options you wish to set. All options have defaults, so you only need to set the options that
18371853
are different. The default options are shown below.
18381854
1839-
If using custom classes (such as the CountingActiveRecordOperationsProcessor, or a CustomPaginator),
1840-
be sure to require them at the top of the initializer before usage.
1855+
If using custom classes (such as a CustomPaginator), be sure to require them at the top of the initializer before usage.
18411856
18421857
```ruby
18431858
JSONAPI.configure do |config|
@@ -1847,8 +1862,9 @@ JSONAPI.configure do |config|
18471862
#:underscored_route, :camelized_route, :dasherized_route, or custom
18481863
config.route_format = :dasherized_route
18491864
1850-
#:basic, :active_record, or custom
1851-
config.operations_processor = :active_record
1865+
# Default Processor, used if a resource specific one is not defined.
1866+
# Must be a class
1867+
config.default_processor_klass = JSONAPI::Processor
18521868
18531869
#:integer, :uuid, :string, or custom (provide a proc)
18541870
config.resource_key_type = :integer

jsonapi-resources.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
1313
spec.homepage = 'https://github.com/cerebris/jsonapi-resources'
1414
spec.license = 'MIT'
1515

16-
spec.files = `git ls-files -z`.split("\x0")
16+
spec.files = Dir.glob("{bin,lib}/**/*") + %w(LICENSE.txt README.md)
1717
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
1818
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
1919
spec.require_paths = ['lib']

lib/jsonapi-resources.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
require 'jsonapi/exceptions'
1414
require 'jsonapi/error'
1515
require 'jsonapi/error_codes'
16-
require 'jsonapi/request'
17-
require 'jsonapi/operations_processor'
18-
require 'jsonapi/active_record_operations_processor'
16+
require 'jsonapi/request_parser'
17+
require 'jsonapi/operation_dispatcher'
18+
require 'jsonapi/processor'
1919
require 'jsonapi/relationship'
2020
require 'jsonapi/include_directives'
2121
require 'jsonapi/operation_result'

lib/jsonapi/active_record_operations_processor.rb

Lines changed: 0 additions & 35 deletions
This file was deleted.

lib/jsonapi/acts_as_resource_controller.rb

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
module JSONAPI
44
module ActsAsResourceController
5+
MEDIA_TYPE_MATCHER = /(.+".+"[^,]*|[^,]+)/
6+
ALL_MEDIA_TYPES = '*/*'
57

68
def self.included(base)
79
base.extend ClassMethods
10+
base.include Callbacks
811
base.before_action :ensure_correct_media_type, only: [:create, :update, :create_relationship, :update_relationship]
12+
base.before_action :ensure_valid_accept_media_type
913
base.cattr_reader :server_error_callbacks
14+
base.define_jsonapi_resources_callbacks :process_operations
1015
end
1116

1217
def index
@@ -54,23 +59,44 @@ def get_related_resources
5459
end
5560

5661
def process_request
57-
@request = JSONAPI::Request.new(params, context: context,
58-
key_formatter: key_formatter,
59-
server_error_callbacks: (self.class.server_error_callbacks || []))
62+
@request = JSONAPI::RequestParser.new(params, context: context,
63+
key_formatter: key_formatter,
64+
server_error_callbacks: (self.class.server_error_callbacks || []))
6065
unless @request.errors.empty?
6166
render_errors(@request.errors)
6267
else
63-
operation_results = create_operations_processor.process(@request)
64-
render_results(operation_results)
68+
process_operations
69+
render_results(@operation_results)
6570
end
6671

6772
rescue => e
6873
handle_exceptions(e)
6974
end
7075

71-
# set the operations processor in the configuration or override this to use another operations processor
72-
def create_operations_processor
73-
JSONAPI.configuration.operations_processor.new
76+
def process_operations
77+
run_callbacks :process_operations do
78+
@operation_results = operation_dispatcher.process(@request.operations)
79+
end
80+
end
81+
82+
def transaction
83+
lambda { |&block|
84+
ActiveRecord::Base.transaction do
85+
block.yield
86+
end
87+
}
88+
end
89+
90+
def rollback
91+
lambda {
92+
fail ActiveRecord::Rollback
93+
}
94+
end
95+
96+
def operation_dispatcher
97+
@operation_dispatcher ||= JSONAPI::OperationDispatcher.new(transaction: transaction,
98+
rollback: rollback,
99+
server_error_callbacks: @request.server_error_callbacks)
74100
end
75101

76102
private
@@ -99,6 +125,36 @@ def ensure_correct_media_type
99125
handle_exceptions(e)
100126
end
101127

128+
def ensure_valid_accept_media_type
129+
if invalid_accept_media_type?
130+
fail JSONAPI::Exceptions::NotAcceptableError.new(request.accept)
131+
end
132+
rescue => e
133+
handle_exceptions(e)
134+
end
135+
136+
def invalid_accept_media_type?
137+
media_types = media_types_for('Accept')
138+
139+
return false if media_types.blank? || media_types.include?(ALL_MEDIA_TYPES)
140+
141+
jsonapi_media_types = media_types.select do |media_type|
142+
media_type.include?(JSONAPI::MEDIA_TYPE)
143+
end
144+
145+
jsonapi_media_types.size.zero? ||
146+
jsonapi_media_types.none? do |media_type|
147+
media_type == JSONAPI::MEDIA_TYPE
148+
end
149+
end
150+
151+
def media_types_for(header)
152+
(request.headers[header] || '')
153+
.match(MEDIA_TYPE_MATCHER)
154+
.to_a
155+
.map(&:strip)
156+
end
157+
102158
# override to set context
103159
def context
104160
{}

0 commit comments

Comments
 (0)