Skip to content

Commit 4fa1ed4

Browse files
committed
Merge remote-tracking branch 'remotes/origin/master' into rails5
2 parents 831e875 + 7130207 commit 4fa1ed4

7 files changed

Lines changed: 464 additions & 43 deletions

File tree

README.md

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# JSONAPI::Resources [![Build Status](https://secure.travis-ci.org/cerebris/jsonapi-resources.svg?branch=master)](http://travis-ci.org/cerebris/jsonapi-resources) [![Code Climate](https://codeclimate.com/github/cerebris/jsonapi-resources/badges/gpa.svg)](https://codeclimate.com/github/cerebris/jsonapi-resources)
1+
# JSONAPI::Resources [![Gem Version](https://badge.fury.io/rb/jsonapi-resources.svg)](https://badge.fury.io/rb/jsonapi-resources) [![Build Status](https://secure.travis-ci.org/cerebris/jsonapi-resources.svg?branch=master)](http://travis-ci.org/cerebris/jsonapi-resources) [![Code Climate](https://codeclimate.com/github/cerebris/jsonapi-resources/badges/gpa.svg)](https://codeclimate.com/github/cerebris/jsonapi-resources)
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: This Rails 5 branch is a work in progress. 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.**
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.**
66

77
`JSONAPI::Resources`, or "JR", provides a framework for developing a server that complies with the
88
[JSON API](http://jsonapi.org/) specification.
@@ -29,6 +29,7 @@ backed by ActiveRecord models or by custom objects.
2929
* [Pagination] (#pagination)
3030
* [Included relationships (side-loading resources)] (#included-relationships-side-loading-resources)
3131
* [Resource meta] (#resource-meta)
32+
* [Custom Links] (#resource-meta)
3233
* [Callbacks] (#callbacks)
3334
* [Controllers] (#controllers)
3435
* [Namespaces] (#namespaces)
@@ -787,12 +788,22 @@ The `paged` `paginator` returns results based on pages of a fixed size. Valid `p
787788
If `number` is omitted the first page is returned. If `size` is omitted the `default_page_size` from the configuration
788789
settings is used.
789790

791+
```
792+
GET /articles?page%5Bnumber%5D=10&page%5Bsize%5D=10 HTTP/1.1
793+
Accept: application/vnd.api+json
794+
```
795+
790796
###### Offset Paginator
791797

792798
The `offset` `paginator` returns results based on an offset from the beginning of the resultset. Valid `page` parameters
793799
are `offset` and `limit`. If `offset` is omitted a value of 0 will be used. If `limit` is omitted the `default_page_size`
794800
from the configuration settings is used.
795801

802+
```
803+
GET /articles?page%5Blimit%5D=10&page%5Boffset%5D=10 HTTP/1.1
804+
Accept: application/vnd.api+json
805+
```
806+
796807
###### Custom Paginators
797808

798809
Custom `paginators` can be used. These should derive from `Paginator`. The `apply` method takes a `relation` and
@@ -931,6 +942,72 @@ method is called with an `options` has. The `options` hash will contain the foll
931942
* `:serializer` -> the serializer instance
932943
* `:serialization_options` -> the contents of the `serialization_options` method on the controller.
933944

945+
#### Custom Links
946+
947+
Custom links can be included for each resource by overriding the `custom_links` method. If a non empty hash is returned from `custom_links`, it will be merged with the default links hash containing the resource's `self` link. The `custom_links` method is called with the same `options` hash used by for [resource meta information](#resource-meta). The `options` hash contains the following:
948+
949+
* `:serializer` -> the serializer instance
950+
* `:serialization_options` -> the contents of the `serialization_options` method on the controller.
951+
952+
For example:
953+
954+
```ruby
955+
class CityCouncilMeeting < JSONAPI::Resource
956+
attribute :title, :location, :approved
957+
958+
def custom_links(options)
959+
{ minutes: options[:serialzer].link_builder.self_link(self) + "/minutes" }
960+
end
961+
end
962+
```
963+
964+
This will create a custom link with the key `minutes`, which will be merged with the default `self` link, like so:
965+
966+
```json
967+
{
968+
"data": [
969+
{
970+
"id": "1",
971+
"type": "cityCouncilMeetings",
972+
"links": {
973+
"self": "http://city.gov/api/city-council-meetings/1",
974+
"minutes": "http://city.gov/api/city-council-meetings/1/minutes"
975+
},
976+
"attributes": {...}
977+
},
978+
//...
979+
]
980+
}
981+
```
982+
983+
Of course, the `custom_links` method can include logic to include links only when relevant:
984+
985+
````ruby
986+
class CityCouncilMeeting < JSONAPI::Resource
987+
attribute :title, :location, :approved
988+
989+
delegate :approved?, to: :model
990+
991+
def custom_links(options)
992+
extra_links = {}
993+
if approved?
994+
extra_links[:minutes] = options[:serialzer].link_builder.self_link(self) + "/minutes"
995+
end
996+
extra_links
997+
end
998+
end
999+
```
1000+
1001+
It's also possibly to suppress the default `self` link by returning a hash with `{self: nil}`:
1002+
1003+
````ruby
1004+
class Selfless < JSONAPI::Resource
1005+
def custom_links(options)
1006+
{self: nil}
1007+
end
1008+
end
1009+
```
1010+
9341011
#### Callbacks
9351012
9361013
`ActiveSupport::Callbacks` is used to provide callback functionality, so the behavior is very similar to what you may be
@@ -1020,6 +1097,14 @@ JSONAPI.configure do |config|
10201097
end
10211098
```
10221099
1100+
To use a specific `OperationsProcessor` in a `ResourceController`, override the `create_operations_processor` method:
1101+
1102+
```ruby
1103+
def create_operations_processor
1104+
CountingActiveRecordOperationsProcessor.new
1105+
end
1106+
```
1107+
10231108
The callback code will be called after each find. It will use the same options as the find operation, without the
10241109
pagination, to collect the record count. This is stored in the `operation_meta`, which will be returned in the top level
10251110
meta element.

lib/jsonapi/paginator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def links_page_params(options = {})
5858

5959
previous_offset = 0 if previous_offset < 0
6060

61-
links_page_params['previous'] = {
61+
links_page_params['prev'] = {
6262
'offset' => previous_offset,
6363
'limit' => @limit
6464
}

lib/jsonapi/resource.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@ def meta(_options)
152152
{}
153153
end
154154

155+
# Override this to return custom links
156+
# must return a hash, which will be merged with the default { self: 'self-url' } links hash
157+
# links keys will be not be formatted with the key formatter for the serializer by default.
158+
# They can however use the serializer's format_key and format_value methods if desired
159+
# the _options hash will contain the serializer and the serialization_options
160+
def custom_links(_options)
161+
{}
162+
end
163+
155164
private
156165

157166
def save
@@ -531,7 +540,11 @@ def apply_filter(records, filter, value, options = {})
531540
strategy = _allowed_filters.fetch(filter.to_sym, Hash.new)[:apply]
532541

533542
if strategy
534-
strategy.call(records, value, options)
543+
if strategy.is_a?(Symbol) || strategy.is_a?(String)
544+
send(strategy, records, value, options)
545+
else
546+
strategy.call(records, value, options)
547+
end
535548
else
536549
records.where(filter => value)
537550
end
@@ -630,7 +643,12 @@ def verify_filter(filter, raw, context = nil)
630643
strategy = _allowed_filters.fetch(filter, Hash.new)[:verify]
631644

632645
if strategy
633-
[filter, strategy.call(filter_values, context)]
646+
if strategy.is_a?(Symbol) || strategy.is_a?(String)
647+
values = send(strategy, filter_values, context)
648+
else
649+
values = strategy.call(filter_values, context)
650+
end
651+
[filter, values]
634652
else
635653
if is_filter_relationship?(filter)
636654
verify_relationship_filter(filter, filter_values, context)

lib/jsonapi/resource_serializer.rb

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,18 @@ def object_hash(source, include_directives)
114114

115115
obj_hash['type'] = format_key(source.class._type.to_s)
116116

117-
links = relationship_links(source)
117+
links = links_hash(source)
118118
obj_hash['links'] = links unless links.empty?
119119

120-
attributes = attribute_hash(source)
120+
attributes = attributes_hash(source)
121121
obj_hash['attributes'] = attributes unless attributes.empty?
122122

123-
relationships = relationship_data(source, include_directives)
123+
relationships = relationships_hash(source, include_directives)
124124
obj_hash['relationships'] = relationships unless relationships.nil? || relationships.empty?
125125

126-
meta = source.meta(custom_generation_options)
127-
if meta.is_a?(Hash) && !meta.empty?
128-
obj_hash['meta'] = meta
129-
end
126+
meta = meta_hash(source)
127+
obj_hash['meta'] = meta unless meta.empty?
128+
130129
obj_hash
131130
end
132131

@@ -139,7 +138,7 @@ def requested_fields(klass)
139138
end
140139
end
141140

142-
def attribute_hash(source)
141+
def attributes_hash(source)
143142
requested = requested_fields(source.class)
144143
fields = source.fetchable_fields & source.class._attributes.keys.to_a
145144
fields = requested & fields unless requested.nil?
@@ -159,7 +158,23 @@ def custom_generation_options
159158
}
160159
end
161160

162-
def relationship_data(source, include_directives)
161+
def meta_hash(source)
162+
meta = source.meta(custom_generation_options)
163+
(meta.is_a?(Hash) && meta) || {}
164+
end
165+
166+
def links_hash(source)
167+
{
168+
self: link_builder.self_link(source)
169+
}.merge(custom_links_hash(source)).compact
170+
end
171+
172+
def custom_links_hash(source)
173+
custom_links = source.custom_links(custom_generation_options)
174+
(custom_links.is_a?(Hash) && custom_links) || {}
175+
end
176+
177+
def relationships_hash(source, include_directives)
163178
relationships = source.class._relationships
164179
requested = requested_fields(source.class)
165180
fields = relationships.keys
@@ -197,7 +212,7 @@ def relationship_data(source, include_directives)
197212
if include_linkage && !relationships_only
198213
add_included_object(id, object_hash(resource, ia))
199214
elsif include_linked_children || relationships_only
200-
relationship_data(resource, ia)
215+
relationships_hash(resource, ia)
201216
end
202217
end
203218
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
@@ -208,7 +223,7 @@ def relationship_data(source, include_directives)
208223
if include_linkage && !relationships_only
209224
add_included_object(id, object_hash(resource, ia))
210225
elsif include_linked_children || relationships_only
211-
relationship_data(resource, ia)
226+
relationships_hash(resource, ia)
212227
end
213228
end
214229
end
@@ -217,13 +232,6 @@ def relationship_data(source, include_directives)
217232
end
218233
end
219234

220-
def relationship_links(source)
221-
links = {}
222-
links[:self] = link_builder.self_link(source)
223-
224-
links
225-
end
226-
227235
def already_serialized?(type, id)
228236
type = format_key(type)
229237
@included_objects.key?(type) && @included_objects[type].key?(id)
@@ -299,7 +307,7 @@ def foreign_key_types_and_values(source, relationship)
299307
if relationship.is_a?(JSONAPI::Relationship::ToMany)
300308
if relationship.polymorphic?
301309
source._model.public_send(relationship.name).pluck(:type, :id).map do |type, id|
302-
[type.pluralize, IdValueFormatter.format(id)]
310+
[type.underscore.pluralize, IdValueFormatter.format(id)]
303311
end
304312
else
305313
source.public_send(relationship.foreign_key).map do |value|

0 commit comments

Comments
 (0)