jsonapi-resources 0.7.1.beta2 → 0.8.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +60 -4
- data/lib/jsonapi/configuration.rb +16 -1
- data/lib/jsonapi/include_directives.rb +16 -3
- data/lib/jsonapi/operation_dispatcher.rb +2 -1
- data/lib/jsonapi/relationship.rb +7 -2
- data/lib/jsonapi/relationship_builder.rb +157 -0
- data/lib/jsonapi/request_parser.rb +10 -6
- data/lib/jsonapi/resource.rb +164 -145
- data/lib/jsonapi/resource_serializer.rb +25 -14
- data/lib/jsonapi/resources/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d825825bdf720bb3b498b9975b9455c52df1ed92
|
4
|
+
data.tar.gz: fad2b60d570a85cadb6fc44a073cd316d0ed3650
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f10510695282091866599ec8ca994d25ba348e98a12b8cc9501ab9e0a1b6f0f054e43ceb0091078cac26d05b43c6971db22a73350662186b6126d4ce888520d
|
7
|
+
data.tar.gz: 9aaa540340d1426f24269dc2bf3813931256b3dfb1e9e0c667967ee103262033ad8834be14a564e1fe817820392e6e80d4001fda3763a8155e0fa4686c4efe74
|
data/README.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
[![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)
|
4
4
|
|
5
|
+
**NOTE:** This README is the documentation for `JSONAPI::Resources`. If you are viewing this at the
|
6
|
+
[project page on Github](https://github.com/cerebris/jsonapi-resources) you are viewing the documentation for the `master`
|
7
|
+
branch. This may contain information that is not relevant to the release you are using. Please see the README for the
|
8
|
+
[version](https://github.com/cerebris/jsonapi-resources/releases) you are using.
|
9
|
+
|
10
|
+
---
|
11
|
+
|
5
12
|
`JSONAPI::Resources`, or "JR", provides a framework for developing a server that complies with the
|
6
13
|
[JSON API](http://jsonapi.org/) specification.
|
7
14
|
|
@@ -43,6 +50,7 @@ backed by ActiveRecord models or by custom objects.
|
|
43
50
|
* [Key Format] (#key-format)
|
44
51
|
* [Routing] (#routing)
|
45
52
|
* [Nested Routes] (#nested-routes)
|
53
|
+
* [Authorization](#authorization)
|
46
54
|
* [Configuration] (#configuration)
|
47
55
|
* [Contributing] (#contributing)
|
48
56
|
* [License] (#license)
|
@@ -91,7 +99,7 @@ class ContactResource < JSONAPI::Resource
|
|
91
99
|
end
|
92
100
|
```
|
93
101
|
|
94
|
-
A jsonapi-resource generator is
|
102
|
+
A jsonapi-resource generator is available
|
95
103
|
```
|
96
104
|
rails generate jsonapi:resource contact
|
97
105
|
```
|
@@ -333,6 +341,21 @@ The request will look something like:
|
|
333
341
|
GET /books?include=author&sort=author.name
|
334
342
|
```
|
335
343
|
|
344
|
+
###### Default sorting
|
345
|
+
|
346
|
+
By default JR sorts ascending on the `id` of the primary resource, unless the request specifies an alternate sort order.
|
347
|
+
To override this you may override the `self.default_sort` on a `resource`. `default_sort` should return an array of
|
348
|
+
`sort_param` hashes. A `sort_param` hash contains a `field` and a `direction`, with `direction` being either `:asc` or
|
349
|
+
`:desc`.
|
350
|
+
|
351
|
+
For example:
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
def self.default_sort
|
355
|
+
[{field: 'name_last', direction: :desc}, {field: 'name_first', direction: :desc}]
|
356
|
+
end
|
357
|
+
```
|
358
|
+
|
336
359
|
##### Attribute Formatting
|
337
360
|
|
338
361
|
Attributes can have a `Format`. By default all attributes use the default formatter. If an attribute has the `format`
|
@@ -526,10 +549,14 @@ The relationship methods (`relationship`, `has_one`, and `has_many`) support the
|
|
526
549
|
* `polymorphic` - set to true to identify relationships that are polymorphic.
|
527
550
|
* `relation_name` - the name of the relation to use on the model. A lambda may be provided which allows conditional selection of the relation based on the context.
|
528
551
|
* `always_include_linkage_data` - if set to true, the relationship includes linkage data. Defaults to false if not set.
|
552
|
+
* `eager_load_on_include` - if set to false, will not include this relationship in join SQL when requested via an include. You usually want to leave this on, but it will break 'relationships' which are not active record, for example if you want to expose a tree using the `ancestry` gem or similar, or the SQL query becomes too large to handle. Defaults to true if not set.
|
529
553
|
|
530
554
|
`to_one` relationships support the additional option:
|
531
555
|
* `foreign_key_on` - defaults to `:self`. To indicate that the foreign key is on the related resource specify `:related`.
|
532
556
|
|
557
|
+
`to_many` relationships support the additional option:
|
558
|
+
* `reflect` - defaults to `true`. To indicate that updates to the relationship are performed on the related resource, if relationship reflection is turned on. See [Configuration] (#configuration)
|
559
|
+
|
533
560
|
Examples:
|
534
561
|
|
535
562
|
```ruby
|
@@ -1118,6 +1145,13 @@ Callbacks can be defined for the following `JSONAPI::Resource` events:
|
|
1118
1145
|
- `:remove_to_one_link`
|
1119
1146
|
- `:replace_fields`
|
1120
1147
|
|
1148
|
+
###### Relationship Reflection
|
1149
|
+
|
1150
|
+
By default updates to relationships only invoke callbacks on the primary
|
1151
|
+
Resource. By setting the `use_relationship_reflection` [Configuration] (#configuration) option
|
1152
|
+
updates to `has_many` relationships will occur on the related resource, triggering
|
1153
|
+
callbacks on both resources.
|
1154
|
+
|
1121
1155
|
##### `JSONAPI::Processor` Callbacks
|
1122
1156
|
|
1123
1157
|
Callbacks can also be defined for `JSONAPI::Processor` events:
|
@@ -1174,7 +1208,7 @@ rails generate jsonapi:controller contact
|
|
1174
1208
|
###### ResourceControllerMetal
|
1175
1209
|
|
1176
1210
|
`JSONAPI::Resources` also provides an alternative class to `ResourceController` called `ResourceControllerMetal`.
|
1177
|
-
In order to provide a lighter weight controller option this strips the controller down to just the classes needed
|
1211
|
+
In order to provide a lighter weight controller option this strips the controller down to just the classes needed
|
1178
1212
|
to work with `JSONAPI::Resources`.
|
1179
1213
|
|
1180
1214
|
For example:
|
@@ -1186,7 +1220,7 @@ end
|
|
1186
1220
|
```
|
1187
1221
|
|
1188
1222
|
Note: This may not provide all of the expected controller capabilities if you are using additional gems such as DoorKeeper.
|
1189
|
-
|
1223
|
+
|
1190
1224
|
###### Serialization Options
|
1191
1225
|
|
1192
1226
|
Additional options can be passed to the serializer using the `serialization_options` method.
|
@@ -1222,7 +1256,7 @@ JSONAPI::Resources supports namespacing of controllers and resources. With names
|
|
1222
1256
|
|
1223
1257
|
If you namespace your controller it will require a namespaced resource.
|
1224
1258
|
|
1225
|
-
In the following example we have a `resource` that isn't namespaced, and one
|
1259
|
+
In the following example we have a `resource` that isn't namespaced, and one that has now been namespaced. There are
|
1226
1260
|
slight differences between the two resources, as might be seen in a new version of an API:
|
1227
1261
|
|
1228
1262
|
```ruby
|
@@ -1862,6 +1896,15 @@ phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) co
|
|
1862
1896
|
|
1863
1897
|
```
|
1864
1898
|
|
1899
|
+
### Authorization
|
1900
|
+
|
1901
|
+
Currently `json-api-resources` doesn't come with built-in primitives for authorization. However multiple users of the framework have come up with different approaches, check out:
|
1902
|
+
|
1903
|
+
- [jsonapi-authorization](https://github.com/venuu/jsonapi-authorization)
|
1904
|
+
- [pundit-resources](https://github.com/togglepro/pundit-resources)
|
1905
|
+
|
1906
|
+
Refer to the comments/discussion [here](https://github.com/cerebris/jsonapi-resources/issues/16#issuecomment-222438975) for the differences between approaches
|
1907
|
+
|
1865
1908
|
## Configuration
|
1866
1909
|
|
1867
1910
|
JR has a few configuration options. Some have already been mentioned above. To set configuration options create an
|
@@ -1928,6 +1971,19 @@ JSONAPI.configure do |config|
|
|
1928
1971
|
# Controls the serialization of resource linkage for non compound documents
|
1929
1972
|
# NOTE: always_include_to_many_linkage_data is not currently implemented
|
1930
1973
|
config.always_include_to_one_linkage_data = false
|
1974
|
+
|
1975
|
+
# Relationship reflection invokes the related resource when updates
|
1976
|
+
# are made to a has_many relationship. By default relationship_reflection
|
1977
|
+
# is turned off because it imposes a small performance penalty.
|
1978
|
+
config.use_relationship_reflection = false
|
1979
|
+
|
1980
|
+
# Allows transactions for creating and updating records
|
1981
|
+
# Set this to false if your backend does not support transactions (e.g. Mongodb)
|
1982
|
+
config.allow_transactions = true
|
1983
|
+
|
1984
|
+
# Formatter Caching
|
1985
|
+
# Set to false to disable caching of string operations on keys and links.
|
1986
|
+
config.cache_formatters = true
|
1931
1987
|
end
|
1932
1988
|
```
|
1933
1989
|
|
@@ -21,10 +21,12 @@ module JSONAPI
|
|
21
21
|
:top_level_meta_record_count_key,
|
22
22
|
:top_level_meta_include_page_count,
|
23
23
|
:top_level_meta_page_count_key,
|
24
|
+
:allow_transactions,
|
24
25
|
:exception_class_whitelist,
|
25
26
|
:always_include_to_one_linkage_data,
|
26
27
|
:always_include_to_many_linkage_data,
|
27
|
-
:cache_formatters
|
28
|
+
:cache_formatters,
|
29
|
+
:use_relationship_reflection
|
28
30
|
|
29
31
|
def initialize
|
30
32
|
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
@@ -80,9 +82,18 @@ module JSONAPI
|
|
80
82
|
# for a Resource.
|
81
83
|
self.default_processor_klass = JSONAPI::Processor
|
82
84
|
|
85
|
+
# Allows transactions for creating and updating records
|
86
|
+
# Set this to false if your backend does not support transactions (e.g. Mongodb)
|
87
|
+
self.allow_transactions = true
|
88
|
+
|
83
89
|
# Formatter Caching
|
84
90
|
# Set to false to disable caching of string operations on keys and links.
|
85
91
|
self.cache_formatters = true
|
92
|
+
|
93
|
+
# Relationship reflection invokes the related resource when updates
|
94
|
+
# are made to a has_many relationship. By default relationship_reflection
|
95
|
+
# is turned off because it imposes a small performance penalty.
|
96
|
+
self.use_relationship_reflection = false
|
86
97
|
end
|
87
98
|
|
88
99
|
def cache_formatters=(bool)
|
@@ -172,6 +183,8 @@ module JSONAPI
|
|
172
183
|
|
173
184
|
attr_writer :top_level_meta_page_count_key
|
174
185
|
|
186
|
+
attr_writer :allow_transactions
|
187
|
+
|
175
188
|
attr_writer :exception_class_whitelist
|
176
189
|
|
177
190
|
attr_writer :always_include_to_one_linkage_data
|
@@ -179,6 +192,8 @@ module JSONAPI
|
|
179
192
|
attr_writer :always_include_to_many_linkage_data
|
180
193
|
|
181
194
|
attr_writer :raise_if_parameters_not_allowed
|
195
|
+
|
196
|
+
attr_writer :use_relationship_reflection
|
182
197
|
end
|
183
198
|
|
184
199
|
class << self
|
@@ -19,7 +19,9 @@ module JSONAPI
|
|
19
19
|
# }
|
20
20
|
# }
|
21
21
|
|
22
|
-
def initialize(includes_array)
|
22
|
+
def initialize(resource_klass, includes_array, force_eager_load: false)
|
23
|
+
@resource_klass = resource_klass
|
24
|
+
@force_eager_load = force_eager_load
|
23
25
|
@include_directives_hash = { include_related: {} }
|
24
26
|
includes_array.each do |include|
|
25
27
|
parse_include(include)
|
@@ -38,16 +40,27 @@ module JSONAPI
|
|
38
40
|
|
39
41
|
def get_related(current_path)
|
40
42
|
current = @include_directives_hash
|
43
|
+
current_resource_klass = @resource_klass
|
41
44
|
current_path.split('.').each do |fragment|
|
42
45
|
fragment = fragment.to_sym
|
43
|
-
|
46
|
+
|
47
|
+
if current_resource_klass
|
48
|
+
current_relationship = current_resource_klass._relationships[fragment]
|
49
|
+
current_resource_klass = current_relationship.try(:resource_klass)
|
50
|
+
else
|
51
|
+
warn "[RELATIONSHIP NOT FOUND] Relationship could not be found for #{current_path}."
|
52
|
+
end
|
53
|
+
|
54
|
+
include_in_join = @force_eager_load || !current_relationship || current_relationship.eager_load_on_include
|
55
|
+
|
56
|
+
current[:include_related][fragment] ||= { include: false, include_related: {}, include_in_join: include_in_join }
|
44
57
|
current = current[:include_related][fragment]
|
45
58
|
end
|
46
59
|
current
|
47
60
|
end
|
48
61
|
|
49
62
|
def get_includes(directive)
|
50
|
-
directive[:include_related].map do |name, directive|
|
63
|
+
directive[:include_related].select { |k,v| v[:include_in_join] }.map do |name, directive|
|
51
64
|
sub = get_includes(directive)
|
52
65
|
sub.any? ? { name => sub } : name
|
53
66
|
end
|
@@ -16,9 +16,10 @@ module JSONAPI
|
|
16
16
|
# Use transactions if more than one operation and if one of the operations can be transactional
|
17
17
|
# Even if transactional transactions won't be used unless the derived OperationsProcessor supports them.
|
18
18
|
transactional = false
|
19
|
+
|
19
20
|
operations.each do |operation|
|
20
21
|
transactional |= operation.transactional?
|
21
|
-
end
|
22
|
+
end if JSONAPI.configuration.allow_transactions
|
22
23
|
|
23
24
|
transaction(transactional) do
|
24
25
|
# Links and meta data global to the set of operations
|
data/lib/jsonapi/relationship.rb
CHANGED
@@ -2,7 +2,7 @@ module JSONAPI
|
|
2
2
|
class Relationship
|
3
3
|
attr_reader :acts_as_set, :foreign_key, :options, :name,
|
4
4
|
:class_name, :polymorphic, :always_include_linkage_data,
|
5
|
-
:parent_resource
|
5
|
+
:parent_resource, :eager_load_on_include
|
6
6
|
|
7
7
|
def initialize(name, options = {})
|
8
8
|
@name = name.to_s
|
@@ -13,6 +13,7 @@ module JSONAPI
|
|
13
13
|
@relation_name = options.fetch(:relation_name, @name)
|
14
14
|
@polymorphic = options.fetch(:polymorphic, false) == true
|
15
15
|
@always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true
|
16
|
+
@eager_load_on_include = options.fetch(:eager_load_on_include, true) == true
|
16
17
|
end
|
17
18
|
|
18
19
|
alias_method :polymorphic?, :polymorphic
|
@@ -74,15 +75,19 @@ module JSONAPI
|
|
74
75
|
end
|
75
76
|
|
76
77
|
def polymorphic_type
|
77
|
-
"#{
|
78
|
+
"#{name}_type" if polymorphic?
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
81
82
|
class ToMany < Relationship
|
83
|
+
attr_reader :reflect, :inverse_relationship
|
84
|
+
|
82
85
|
def initialize(name, options = {})
|
83
86
|
super
|
84
87
|
@class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
|
85
88
|
@foreign_key ||= "#{name.to_s.singularize}_ids".to_sym
|
89
|
+
@reflect = options.fetch(:reflect, true) == true
|
90
|
+
@inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type.to_s.singularize.to_sym) if parent_resource
|
86
91
|
end
|
87
92
|
end
|
88
93
|
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class RelationshipBuilder
|
3
|
+
attr_reader :model_class, :options, :relationship_class
|
4
|
+
delegate :register_relationship, to: :@resource_class
|
5
|
+
|
6
|
+
def initialize(relationship_class, model_class, options)
|
7
|
+
@relationship_class = relationship_class
|
8
|
+
@model_class = model_class
|
9
|
+
@resource_class = options[:parent_resource]
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def define_relationship_methods(relationship_name)
|
14
|
+
# Initialize from an ActiveRecord model's properties
|
15
|
+
if model_class && model_class.ancestors.collect{|ancestor| ancestor.name}.include?('ActiveRecord::Base')
|
16
|
+
model_association = model_class.reflect_on_association(relationship_name)
|
17
|
+
if model_association
|
18
|
+
options[:class_name] ||= model_association.class_name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
relationship = register_relationship(
|
23
|
+
relationship_name,
|
24
|
+
relationship_class.new(relationship_name, options)
|
25
|
+
)
|
26
|
+
|
27
|
+
foreign_key = define_foreign_key_setter(relationship.foreign_key)
|
28
|
+
|
29
|
+
case relationship
|
30
|
+
when JSONAPI::Relationship::ToOne
|
31
|
+
associated = define_resource_relationship_accessor(:one, relationship_name)
|
32
|
+
args = [relationship, foreign_key, associated, relationship_name]
|
33
|
+
|
34
|
+
relationship.belongs_to? ? build_belongs_to(*args) : build_has_one(*args)
|
35
|
+
when JSONAPI::Relationship::ToMany
|
36
|
+
associated = define_resource_relationship_accessor(:many, relationship_name)
|
37
|
+
|
38
|
+
build_to_many(relationship, foreign_key, associated, relationship_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def define_foreign_key_setter(foreign_key)
|
43
|
+
define_on_resource "#{foreign_key}=" do |value|
|
44
|
+
@model.method("#{foreign_key}=").call(value)
|
45
|
+
end
|
46
|
+
foreign_key
|
47
|
+
end
|
48
|
+
|
49
|
+
def define_resource_relationship_accessor(type, relationship_name)
|
50
|
+
associated_records_method_name = {
|
51
|
+
one: "record_for_#{relationship_name}",
|
52
|
+
many: "records_for_#{relationship_name}"
|
53
|
+
}
|
54
|
+
.fetch(type)
|
55
|
+
|
56
|
+
define_on_resource associated_records_method_name do
|
57
|
+
relationship = self.class._relationships[relationship_name]
|
58
|
+
relation_name = relationship.relation_name(context: @context)
|
59
|
+
records_for(relation_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
associated_records_method_name
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_belongs_to(relationship, foreign_key, associated_records_method_name, relationship_name)
|
66
|
+
# Calls method matching foreign key name on model instance
|
67
|
+
define_on_resource foreign_key do
|
68
|
+
@model.method(foreign_key).call
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns instantiated related resource object or nil
|
72
|
+
define_on_resource relationship_name do |options = {}|
|
73
|
+
relationship = self.class._relationships[relationship_name]
|
74
|
+
|
75
|
+
if relationship.polymorphic?
|
76
|
+
associated_model = public_send(associated_records_method_name)
|
77
|
+
resource_klass = self.class.resource_for_model(associated_model) if associated_model
|
78
|
+
return resource_klass.new(associated_model, @context) if resource_klass
|
79
|
+
else
|
80
|
+
resource_klass = relationship.resource_klass
|
81
|
+
if resource_klass
|
82
|
+
associated_model = public_send(associated_records_method_name)
|
83
|
+
return associated_model ? resource_klass.new(associated_model, @context) : nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_has_one(relationship, foreign_key, associated_records_method_name, relationship_name)
|
90
|
+
# Returns primary key name of related resource class
|
91
|
+
define_on_resource foreign_key do
|
92
|
+
relationship = self.class._relationships[relationship_name]
|
93
|
+
|
94
|
+
record = public_send(associated_records_method_name)
|
95
|
+
return nil if record.nil?
|
96
|
+
record.public_send(relationship.resource_klass._primary_key)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns instantiated related resource object or nil
|
100
|
+
define_on_resource relationship_name do |options = {}|
|
101
|
+
relationship = self.class._relationships[relationship_name]
|
102
|
+
|
103
|
+
resource_klass = relationship.resource_klass
|
104
|
+
if resource_klass
|
105
|
+
associated_model = public_send(associated_records_method_name)
|
106
|
+
return associated_model ? resource_klass.new(associated_model, @context) : nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_to_many(relationship, foreign_key, associated_records_method_name, relationship_name)
|
112
|
+
# Returns array of primary keys of related resource classes
|
113
|
+
define_on_resource foreign_key do
|
114
|
+
records = public_send(associated_records_method_name)
|
115
|
+
return records.collect do |record|
|
116
|
+
record.public_send(relationship.resource_klass._primary_key)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns array of instantiated related resource objects
|
121
|
+
define_on_resource relationship_name do |options = {}|
|
122
|
+
relationship = self.class._relationships[relationship_name]
|
123
|
+
|
124
|
+
resource_klass = relationship.resource_klass
|
125
|
+
records = public_send(associated_records_method_name)
|
126
|
+
|
127
|
+
filters = options.fetch(:filters, {})
|
128
|
+
unless filters.nil? || filters.empty?
|
129
|
+
records = resource_klass.apply_filters(records, filters, options)
|
130
|
+
end
|
131
|
+
|
132
|
+
sort_criteria = options.fetch(:sort_criteria, {})
|
133
|
+
unless sort_criteria.nil? || sort_criteria.empty?
|
134
|
+
order_options = relationship.resource_klass.construct_order_options(sort_criteria)
|
135
|
+
records = resource_klass.apply_sort(records, order_options, @context)
|
136
|
+
end
|
137
|
+
|
138
|
+
paginator = options[:paginator]
|
139
|
+
if paginator
|
140
|
+
records = resource_klass.apply_pagination(records, paginator, order_options)
|
141
|
+
end
|
142
|
+
|
143
|
+
return records.collect do |record|
|
144
|
+
if relationship.polymorphic?
|
145
|
+
resource_klass = self.class.resource_for_model(record)
|
146
|
+
end
|
147
|
+
resource_klass.new(record, @context)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def define_on_resource(method_name, &block)
|
153
|
+
return if @resource_class.method_defined?(method_name)
|
154
|
+
@resource_class.inject_method_definition(method_name, block)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -16,7 +16,7 @@ module JSONAPI
|
|
16
16
|
@operations = []
|
17
17
|
@fields = {}
|
18
18
|
@filters = {}
|
19
|
-
@sort_criteria =
|
19
|
+
@sort_criteria = nil
|
20
20
|
@source_klass = nil
|
21
21
|
@source_id = nil
|
22
22
|
@include_directives = nil
|
@@ -225,7 +225,7 @@ module JSONAPI
|
|
225
225
|
include.push(unformat_key(included_resource).to_s)
|
226
226
|
end
|
227
227
|
|
228
|
-
@include_directives = JSONAPI::IncludeDirectives.new(include)
|
228
|
+
@include_directives = JSONAPI::IncludeDirectives.new(@resource_klass, include)
|
229
229
|
end
|
230
230
|
|
231
231
|
def parse_filters(filters)
|
@@ -326,7 +326,8 @@ module JSONAPI
|
|
326
326
|
relationship_type: relationship_type,
|
327
327
|
source_klass: @source_klass,
|
328
328
|
source_id: @source_id,
|
329
|
-
fields: @fields
|
329
|
+
fields: @fields,
|
330
|
+
include_directives: @include_directives
|
330
331
|
)
|
331
332
|
end
|
332
333
|
|
@@ -340,7 +341,8 @@ module JSONAPI
|
|
340
341
|
filters: @source_klass.verify_filters(@filters, @context),
|
341
342
|
sort_criteria: @sort_criteria,
|
342
343
|
paginator: @paginator,
|
343
|
-
fields: @fields
|
344
|
+
fields: @fields,
|
345
|
+
include_directives: @include_directives
|
344
346
|
)
|
345
347
|
end
|
346
348
|
|
@@ -364,7 +366,8 @@ module JSONAPI
|
|
364
366
|
@resource_klass,
|
365
367
|
context: @context,
|
366
368
|
data: data,
|
367
|
-
fields: @fields
|
369
|
+
fields: @fields,
|
370
|
+
include_directives: @include_directives
|
368
371
|
)
|
369
372
|
end
|
370
373
|
rescue JSONAPI::Exceptions::Error => e
|
@@ -632,7 +635,8 @@ module JSONAPI
|
|
632
635
|
context: @context,
|
633
636
|
resource_id: key,
|
634
637
|
data: parse_params(data, updatable_fields),
|
635
|
-
fields: @fields
|
638
|
+
fields: @fields,
|
639
|
+
include_directives: @include_directives
|
636
640
|
)
|
637
641
|
end
|
638
642
|
|
data/lib/jsonapi/resource.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'jsonapi/callbacks'
|
2
|
+
require 'jsonapi/relationship_builder'
|
2
3
|
|
3
4
|
module JSONAPI
|
4
5
|
class Resource
|
@@ -22,6 +23,9 @@ module JSONAPI
|
|
22
23
|
def initialize(model, context)
|
23
24
|
@model = model
|
24
25
|
@context = context
|
26
|
+
@reload_needed = false
|
27
|
+
@changing = false
|
28
|
+
@save_needed = false
|
25
29
|
end
|
26
30
|
|
27
31
|
def _model
|
@@ -63,39 +67,39 @@ module JSONAPI
|
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
66
|
-
def create_to_many_links(relationship_type, relationship_key_values)
|
70
|
+
def create_to_many_links(relationship_type, relationship_key_values, options = {})
|
67
71
|
change :create_to_many_link do
|
68
|
-
_create_to_many_links(relationship_type, relationship_key_values)
|
72
|
+
_create_to_many_links(relationship_type, relationship_key_values, options)
|
69
73
|
end
|
70
74
|
end
|
71
75
|
|
72
|
-
def replace_to_many_links(relationship_type, relationship_key_values)
|
76
|
+
def replace_to_many_links(relationship_type, relationship_key_values, options = {})
|
73
77
|
change :replace_to_many_links do
|
74
|
-
_replace_to_many_links(relationship_type, relationship_key_values)
|
78
|
+
_replace_to_many_links(relationship_type, relationship_key_values, options)
|
75
79
|
end
|
76
80
|
end
|
77
81
|
|
78
|
-
def replace_to_one_link(relationship_type, relationship_key_value)
|
82
|
+
def replace_to_one_link(relationship_type, relationship_key_value, options = {})
|
79
83
|
change :replace_to_one_link do
|
80
|
-
_replace_to_one_link(relationship_type, relationship_key_value)
|
84
|
+
_replace_to_one_link(relationship_type, relationship_key_value, options)
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
84
|
-
def replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type)
|
88
|
+
def replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type, options = {})
|
85
89
|
change :replace_polymorphic_to_one_link do
|
86
|
-
_replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type)
|
90
|
+
_replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type, options)
|
87
91
|
end
|
88
92
|
end
|
89
93
|
|
90
|
-
def remove_to_many_link(relationship_type, key)
|
94
|
+
def remove_to_many_link(relationship_type, key, options = {})
|
91
95
|
change :remove_to_many_link do
|
92
|
-
_remove_to_many_link(relationship_type, key)
|
96
|
+
_remove_to_many_link(relationship_type, key, options)
|
93
97
|
end
|
94
98
|
end
|
95
99
|
|
96
|
-
def remove_to_one_link(relationship_type)
|
100
|
+
def remove_to_one_link(relationship_type, options = {})
|
97
101
|
change :remove_to_one_link do
|
98
|
-
_remove_to_one_link(relationship_type)
|
102
|
+
_remove_to_one_link(relationship_type, options)
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
@@ -189,6 +193,7 @@ module JSONAPI
|
|
189
193
|
|
190
194
|
if defined? @model.save
|
191
195
|
saved = @model.save(validate: false)
|
196
|
+
|
192
197
|
unless saved
|
193
198
|
if @model.errors.present?
|
194
199
|
fail JSONAPI::Exceptions::ValidationErrors.new(self)
|
@@ -199,6 +204,8 @@ module JSONAPI
|
|
199
204
|
else
|
200
205
|
saved = true
|
201
206
|
end
|
207
|
+
@model.reload if @reload_needed
|
208
|
+
@reload_needed = false
|
202
209
|
|
203
210
|
@save_needed = !saved
|
204
211
|
|
@@ -215,34 +222,87 @@ module JSONAPI
|
|
215
222
|
fail JSONAPI::Exceptions::RecordLocked.new(e.message)
|
216
223
|
end
|
217
224
|
|
218
|
-
def
|
225
|
+
def reflect_relationship?(relationship, options)
|
226
|
+
return false if !relationship.reflect ||
|
227
|
+
(!JSONAPI.configuration.use_relationship_reflection || options[:reflected_source])
|
228
|
+
|
229
|
+
inverse_relationship = relationship.resource_klass._relationships[relationship.inverse_relationship]
|
230
|
+
if inverse_relationship.nil?
|
231
|
+
warn "Inverse relationship could not be found for #{self.class.name}.#{relationship.name}. Relationship reflection disabled."
|
232
|
+
return false
|
233
|
+
end
|
234
|
+
true
|
235
|
+
end
|
236
|
+
|
237
|
+
def _create_to_many_links(relationship_type, relationship_key_values, options)
|
219
238
|
relationship = self.class._relationships[relationship_type]
|
220
239
|
|
221
|
-
relationship_key_values
|
222
|
-
|
240
|
+
# check if relationship_key_values are already members of this relationship
|
241
|
+
relation_name = relationship.relation_name(context: @context)
|
242
|
+
existing_relations = @model.public_send(relation_name).where(relationship.primary_key => relationship_key_values)
|
243
|
+
if existing_relations.count > 0
|
244
|
+
# todo: obscure id so not to leak info
|
245
|
+
fail JSONAPI::Exceptions::HasManyRelationExists.new(existing_relations.first.id)
|
246
|
+
end
|
223
247
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
248
|
+
if options[:reflected_source]
|
249
|
+
@model.public_send(relation_name) << options[:reflected_source]._model
|
250
|
+
return :completed
|
251
|
+
end
|
252
|
+
|
253
|
+
# load requested related resources
|
254
|
+
# make sure they all exist (also based on context) and add them to relationship
|
255
|
+
|
256
|
+
related_resources = relationship.resource_klass.find_by_keys(relationship_key_values, context: @context)
|
257
|
+
|
258
|
+
if related_resources.count != relationship_key_values.count
|
259
|
+
# todo: obscure id so not to leak info
|
260
|
+
fail JSONAPI::Exceptions::RecordNotFound.new('unspecified')
|
261
|
+
end
|
262
|
+
|
263
|
+
reflect = reflect_relationship?(relationship, options)
|
264
|
+
|
265
|
+
related_resources.each do |related_resource|
|
266
|
+
if reflect
|
267
|
+
if related_resource.class._relationships[relationship.inverse_relationship].is_a?(JSONAPI::Relationship::ToMany)
|
268
|
+
related_resource.create_to_many_links(relationship.inverse_relationship, [id], reflected_source: self)
|
269
|
+
else
|
270
|
+
related_resource.replace_to_one_link(relationship.inverse_relationship, id, reflected_source: self)
|
271
|
+
end
|
272
|
+
@reload_needed = true
|
229
273
|
else
|
230
|
-
|
274
|
+
@model.public_send(relation_name) << related_resource._model
|
231
275
|
end
|
232
276
|
end
|
233
277
|
|
234
278
|
:completed
|
235
279
|
end
|
236
280
|
|
237
|
-
def _replace_to_many_links(relationship_type, relationship_key_values)
|
281
|
+
def _replace_to_many_links(relationship_type, relationship_key_values, options)
|
238
282
|
relationship = self.class._relationships[relationship_type]
|
239
|
-
|
240
|
-
|
283
|
+
|
284
|
+
reflect = reflect_relationship?(relationship, options)
|
285
|
+
|
286
|
+
if reflect
|
287
|
+
existing = send("#{relationship.foreign_key}")
|
288
|
+
to_delete = existing - (relationship_key_values & existing)
|
289
|
+
to_delete.each do |key|
|
290
|
+
_remove_to_many_link(relationship_type, key, reflected_source: self)
|
291
|
+
end
|
292
|
+
|
293
|
+
to_add = relationship_key_values - (relationship_key_values & existing)
|
294
|
+
_create_to_many_links(relationship_type, to_add, {})
|
295
|
+
|
296
|
+
@reload_needed = true
|
297
|
+
else
|
298
|
+
send("#{relationship.foreign_key}=", relationship_key_values)
|
299
|
+
@save_needed = true
|
300
|
+
end
|
241
301
|
|
242
302
|
:completed
|
243
303
|
end
|
244
304
|
|
245
|
-
def _replace_to_one_link(relationship_type, relationship_key_value)
|
305
|
+
def _replace_to_one_link(relationship_type, relationship_key_value, options)
|
246
306
|
relationship = self.class._relationships[relationship_type]
|
247
307
|
|
248
308
|
send("#{relationship.foreign_key}=", relationship_key_value)
|
@@ -251,7 +311,7 @@ module JSONAPI
|
|
251
311
|
:completed
|
252
312
|
end
|
253
313
|
|
254
|
-
def _replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
|
314
|
+
def _replace_polymorphic_to_one_link(relationship_type, key_value, key_type, options)
|
255
315
|
relationship = self.class._relationships[relationship_type.to_sym]
|
256
316
|
|
257
317
|
_model.public_send("#{relationship.foreign_key}=", key_value)
|
@@ -262,10 +322,29 @@ module JSONAPI
|
|
262
322
|
:completed
|
263
323
|
end
|
264
324
|
|
265
|
-
def _remove_to_many_link(relationship_type, key)
|
266
|
-
|
325
|
+
def _remove_to_many_link(relationship_type, key, options)
|
326
|
+
relationship = self.class._relationships[relationship_type]
|
327
|
+
|
328
|
+
reflect = reflect_relationship?(relationship, options)
|
329
|
+
|
330
|
+
if reflect
|
331
|
+
|
332
|
+
related_resource = relationship.resource_klass.find_by_key(key, context: @context)
|
333
|
+
|
334
|
+
if related_resource.nil?
|
335
|
+
fail JSONAPI::Exceptions::RecordNotFound.new(key)
|
336
|
+
else
|
337
|
+
if related_resource.class._relationships[relationship.inverse_relationship].is_a?(JSONAPI::Relationship::ToMany)
|
338
|
+
related_resource.remove_to_many_link(relationship.inverse_relationship, id, reflected_source: self)
|
339
|
+
else
|
340
|
+
related_resource.remove_to_one_link(relationship.inverse_relationship, reflected_source: self)
|
341
|
+
end
|
342
|
+
end
|
267
343
|
|
268
|
-
|
344
|
+
@reload_needed = true
|
345
|
+
else
|
346
|
+
@model.public_send(relationship.relation_name(context: @context)).delete(key)
|
347
|
+
end
|
269
348
|
|
270
349
|
:completed
|
271
350
|
|
@@ -275,7 +354,7 @@ module JSONAPI
|
|
275
354
|
fail JSONAPI::Exceptions::RecordNotFound.new(key)
|
276
355
|
end
|
277
356
|
|
278
|
-
def _remove_to_one_link(relationship_type)
|
357
|
+
def _remove_to_one_link(relationship_type, options)
|
279
358
|
relationship = self.class._relationships[relationship_type]
|
280
359
|
|
281
360
|
send("#{relationship.foreign_key}=", nil)
|
@@ -404,6 +483,8 @@ module JSONAPI
|
|
404
483
|
ActiveSupport::Deprecation.warn('Id without format is no longer supported. Please remove ids from attributes, or specify a format.')
|
405
484
|
end
|
406
485
|
|
486
|
+
check_duplicate_attribute_name(attr) if options[:format].nil?
|
487
|
+
|
407
488
|
@_attributes ||= {}
|
408
489
|
@_attributes[attr] = options
|
409
490
|
define_method attr do
|
@@ -438,6 +519,15 @@ module JSONAPI
|
|
438
519
|
_add_relationship(Relationship::ToOne, *attrs)
|
439
520
|
end
|
440
521
|
|
522
|
+
def belongs_to(*attrs)
|
523
|
+
ActiveSupport::Deprecation.warn "In #{name} you exposed a `has_one` relationship "\
|
524
|
+
" using the `belongs_to` class method. We think `has_one`" \
|
525
|
+
" is more appropriate. If you know what you're doing," \
|
526
|
+
" and don't want to see this warning again, override the" \
|
527
|
+
" `belongs_to` class method on your resource."
|
528
|
+
_add_relationship(Relationship::ToOne, *attrs)
|
529
|
+
end
|
530
|
+
|
441
531
|
def has_many(*attrs)
|
442
532
|
_add_relationship(Relationship::ToMany, *attrs)
|
443
533
|
end
|
@@ -613,7 +703,7 @@ module JSONAPI
|
|
613
703
|
end
|
614
704
|
|
615
705
|
if required_includes.any?
|
616
|
-
records = apply_includes(records, options.merge(include_directives: IncludeDirectives.new(required_includes)))
|
706
|
+
records = apply_includes(records, options.merge(include_directives: IncludeDirectives.new(self, required_includes, force_eager_load: true)))
|
617
707
|
end
|
618
708
|
|
619
709
|
records
|
@@ -653,14 +743,20 @@ module JSONAPI
|
|
653
743
|
end
|
654
744
|
|
655
745
|
def resources_for(records, context)
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
resource_class = resource_classes[model.class] ||= self.resource_for_model(model)
|
660
|
-
resources.push resource_class.new(model, context)
|
746
|
+
records.collect do |model|
|
747
|
+
resource_class = self.resource_for_model(model)
|
748
|
+
resource_class.new(model, context)
|
661
749
|
end
|
750
|
+
end
|
662
751
|
|
663
|
-
|
752
|
+
def find_by_keys(keys, options = {})
|
753
|
+
context = options[:context]
|
754
|
+
records = records(options)
|
755
|
+
records = apply_includes(records, options)
|
756
|
+
models = records.where({_primary_key => keys})
|
757
|
+
models.collect do |model|
|
758
|
+
self.resource_for_model(model).new(model, context)
|
759
|
+
end
|
664
760
|
end
|
665
761
|
|
666
762
|
def find_by_key(key, options = {})
|
@@ -852,11 +948,17 @@ module JSONAPI
|
|
852
948
|
end
|
853
949
|
end
|
854
950
|
|
951
|
+
def default_sort
|
952
|
+
[{field: 'id', direction: :asc}]
|
953
|
+
end
|
954
|
+
|
855
955
|
def construct_order_options(sort_params)
|
956
|
+
sort_params ||= default_sort
|
957
|
+
|
856
958
|
return {} unless sort_params
|
857
959
|
|
858
960
|
sort_params.each_with_object({}) do |sort, order_hash|
|
859
|
-
field = sort[:field] == 'id' ? _primary_key : sort[:field]
|
961
|
+
field = sort[:field].to_s == 'id' ? _primary_key : sort[:field].to_s
|
860
962
|
order_hash[field] = sort[:direction]
|
861
963
|
end
|
862
964
|
end
|
@@ -865,117 +967,22 @@ module JSONAPI
|
|
865
967
|
options = attrs.extract_options!
|
866
968
|
options[:parent_resource] = self
|
867
969
|
|
868
|
-
attrs.each do |
|
869
|
-
relationship_name = attr.to_sym
|
870
|
-
|
970
|
+
attrs.each do |relationship_name|
|
871
971
|
check_reserved_relationship_name(relationship_name)
|
972
|
+
check_duplicate_relationship_name(relationship_name)
|
872
973
|
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
options[:class_name] ||= model_association.class_name
|
878
|
-
end
|
879
|
-
end
|
880
|
-
|
881
|
-
@_relationships[relationship_name] = relationship = klass.new(relationship_name, options)
|
882
|
-
|
883
|
-
associated_records_method_name = case relationship
|
884
|
-
when JSONAPI::Relationship::ToOne then "record_for_#{relationship_name}"
|
885
|
-
when JSONAPI::Relationship::ToMany then "records_for_#{relationship_name}"
|
886
|
-
end
|
887
|
-
|
888
|
-
foreign_key = relationship.foreign_key
|
889
|
-
|
890
|
-
define_method "#{foreign_key}=" do |value|
|
891
|
-
@model.method("#{foreign_key}=").call(value)
|
892
|
-
end unless method_defined?("#{foreign_key}=")
|
893
|
-
|
894
|
-
define_method associated_records_method_name do
|
895
|
-
relationship = self.class._relationships[relationship_name]
|
896
|
-
relation_name = relationship.relation_name(context: @context)
|
897
|
-
records_for(relation_name)
|
898
|
-
end unless method_defined?(associated_records_method_name)
|
899
|
-
|
900
|
-
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
901
|
-
if relationship.belongs_to?
|
902
|
-
define_method foreign_key do
|
903
|
-
@model.method(foreign_key).call
|
904
|
-
end unless method_defined?(foreign_key)
|
905
|
-
|
906
|
-
define_method relationship_name do |options = {}|
|
907
|
-
relationship = self.class._relationships[relationship_name]
|
908
|
-
|
909
|
-
if relationship.polymorphic?
|
910
|
-
associated_model = public_send(associated_records_method_name)
|
911
|
-
resource_klass = self.class.resource_for_model(associated_model) if associated_model
|
912
|
-
return resource_klass.new(associated_model, @context) if resource_klass
|
913
|
-
else
|
914
|
-
resource_klass = relationship.resource_klass
|
915
|
-
if resource_klass
|
916
|
-
associated_model = public_send(associated_records_method_name)
|
917
|
-
return associated_model ? resource_klass.new(associated_model, @context) : nil
|
918
|
-
end
|
919
|
-
end
|
920
|
-
end unless method_defined?(relationship_name)
|
921
|
-
else
|
922
|
-
define_method foreign_key do
|
923
|
-
relationship = self.class._relationships[relationship_name]
|
924
|
-
|
925
|
-
record = public_send(associated_records_method_name)
|
926
|
-
return nil if record.nil?
|
927
|
-
record.public_send(relationship.resource_klass._primary_key)
|
928
|
-
end unless method_defined?(foreign_key)
|
929
|
-
|
930
|
-
define_method relationship_name do |options = {}|
|
931
|
-
relationship = self.class._relationships[relationship_name]
|
932
|
-
|
933
|
-
resource_klass = relationship.resource_klass
|
934
|
-
if resource_klass
|
935
|
-
associated_model = public_send(associated_records_method_name)
|
936
|
-
return associated_model ? resource_klass.new(associated_model, @context) : nil
|
937
|
-
end
|
938
|
-
end unless method_defined?(relationship_name)
|
939
|
-
end
|
940
|
-
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
941
|
-
define_method foreign_key do
|
942
|
-
records = public_send(associated_records_method_name)
|
943
|
-
return records.collect do |record|
|
944
|
-
record.public_send(relationship.resource_klass._primary_key)
|
945
|
-
end
|
946
|
-
end unless method_defined?(foreign_key)
|
947
|
-
|
948
|
-
define_method relationship_name do |options = {}|
|
949
|
-
relationship = self.class._relationships[relationship_name]
|
950
|
-
|
951
|
-
resource_klass = relationship.resource_klass
|
952
|
-
records = public_send(associated_records_method_name)
|
953
|
-
|
954
|
-
filters = options.fetch(:filters, {})
|
955
|
-
unless filters.nil? || filters.empty?
|
956
|
-
records = resource_klass.apply_filters(records, filters, options)
|
957
|
-
end
|
958
|
-
|
959
|
-
sort_criteria = options.fetch(:sort_criteria, {})
|
960
|
-
unless sort_criteria.nil? || sort_criteria.empty?
|
961
|
-
order_options = relationship.resource_klass.construct_order_options(sort_criteria)
|
962
|
-
records = resource_klass.apply_sort(records, order_options, @context)
|
963
|
-
end
|
974
|
+
JSONAPI::RelationshipBuilder.new(klass, _model_class, options)
|
975
|
+
.define_relationship_methods(relationship_name.to_sym)
|
976
|
+
end
|
977
|
+
end
|
964
978
|
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
979
|
+
# Allows JSONAPI::RelationshipBuilder to access metaprogramming hooks
|
980
|
+
def inject_method_definition(name, body)
|
981
|
+
define_method(name, body)
|
982
|
+
end
|
969
983
|
|
970
|
-
|
971
|
-
|
972
|
-
resource_klass = self.class.resource_for_model(record)
|
973
|
-
end
|
974
|
-
resource_klass.new(record, @context)
|
975
|
-
end
|
976
|
-
end unless method_defined?(relationship_name)
|
977
|
-
end
|
978
|
-
end
|
984
|
+
def register_relationship(name, relationship_object)
|
985
|
+
@_relationships[name] = relationship_object
|
979
986
|
end
|
980
987
|
|
981
988
|
private
|
@@ -1000,6 +1007,18 @@ module JSONAPI
|
|
1000
1007
|
warn "[NAME COLLISION] `#{name}` is a reserved relationship name in #{_resource_name_from_type(_type)}."
|
1001
1008
|
end
|
1002
1009
|
end
|
1010
|
+
|
1011
|
+
def check_duplicate_relationship_name(name)
|
1012
|
+
if _relationships.include?(name.to_sym)
|
1013
|
+
warn "[DUPLICATE RELATIONSHIP] `#{name}` has already been defined in #{_resource_name_from_type(_type)}."
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
def check_duplicate_attribute_name(name)
|
1018
|
+
if _attributes.include?(name.to_sym)
|
1019
|
+
warn "[DUPLICATE ATTRIBUTE] `#{name}` has already been defined in #{_resource_name_from_type(_type)}."
|
1020
|
+
end
|
1021
|
+
end
|
1003
1022
|
end
|
1004
1023
|
end
|
1005
1024
|
end
|
@@ -16,13 +16,14 @@ module JSONAPI
|
|
16
16
|
# serializer_options: additional options that will be passed to resource meta and links lambdas
|
17
17
|
|
18
18
|
def initialize(primary_resource_klass, options = {})
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@
|
24
|
-
@
|
25
|
-
@
|
19
|
+
@primary_resource_klass = primary_resource_klass
|
20
|
+
@primary_class_name = primary_resource_klass._type
|
21
|
+
@fields = options.fetch(:fields, {})
|
22
|
+
@include = options.fetch(:include, [])
|
23
|
+
@include_directives = options[:include_directives]
|
24
|
+
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
25
|
+
@id_formatter = ValueFormatter.value_formatter_for(:id)
|
26
|
+
@link_builder = generate_link_builder(primary_resource_klass, options)
|
26
27
|
@always_include_to_one_linkage_data = options.fetch(:always_include_to_one_linkage_data,
|
27
28
|
JSONAPI.configuration.always_include_to_one_linkage_data)
|
28
29
|
@always_include_to_many_linkage_data = options.fetch(:always_include_to_many_linkage_data,
|
@@ -41,7 +42,7 @@ module JSONAPI
|
|
41
42
|
is_resource_collection = source.respond_to?(:to_ary)
|
42
43
|
|
43
44
|
@included_objects = {}
|
44
|
-
@include_directives ||= JSONAPI::IncludeDirectives.new(@include)
|
45
|
+
@include_directives ||= JSONAPI::IncludeDirectives.new(@primary_resource_klass, @include)
|
45
46
|
|
46
47
|
process_primary(source, @include_directives.include_directives)
|
47
48
|
|
@@ -219,7 +220,8 @@ module JSONAPI
|
|
219
220
|
resources.each do |resource|
|
220
221
|
next if self_referential_and_already_in_source(resource)
|
221
222
|
id = resource.id
|
222
|
-
|
223
|
+
type = resource.class.resource_for_model(resource._model)
|
224
|
+
relationships_only = already_serialized?(type, id)
|
223
225
|
if include_linkage && !relationships_only
|
224
226
|
add_included_object(id, object_hash(resource, ia))
|
225
227
|
elsif include_linked_children || relationships_only
|
@@ -297,9 +299,18 @@ module JSONAPI
|
|
297
299
|
|
298
300
|
# Extracts the foreign key value for a to_one relationship.
|
299
301
|
def foreign_key_value(source, relationship)
|
300
|
-
|
301
|
-
|
302
|
-
|
302
|
+
# If you have direct access to the underlying id, you don't have to load the relationship
|
303
|
+
# which can save quite a lot of time when loading a lot of data.
|
304
|
+
# This does not apply to e.g. has_one :through relationships.
|
305
|
+
if source._model.respond_to?("#{relationship.name}_id")
|
306
|
+
related_resource_id = source._model.public_send("#{relationship.name}_id")
|
307
|
+
return nil unless related_resource_id
|
308
|
+
@id_formatter.format(related_resource_id)
|
309
|
+
else
|
310
|
+
related_resource = source.public_send(relationship.name)
|
311
|
+
return nil unless related_resource
|
312
|
+
@id_formatter.format(related_resource.id)
|
313
|
+
end
|
303
314
|
end
|
304
315
|
|
305
316
|
def foreign_key_types_and_values(source, relationship)
|
@@ -317,8 +328,8 @@ module JSONAPI
|
|
317
328
|
end
|
318
329
|
end
|
319
330
|
else
|
320
|
-
source.public_send(relationship.
|
321
|
-
[relationship.type, @id_formatter.format(value)]
|
331
|
+
source.public_send(relationship.name).map do |value|
|
332
|
+
[relationship.type, @id_formatter.format(value.id)]
|
322
333
|
end
|
323
334
|
end
|
324
335
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-resources
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Gebhardt
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-07-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -172,6 +172,7 @@ files:
|
|
172
172
|
- lib/jsonapi/paginator.rb
|
173
173
|
- lib/jsonapi/processor.rb
|
174
174
|
- lib/jsonapi/relationship.rb
|
175
|
+
- lib/jsonapi/relationship_builder.rb
|
175
176
|
- lib/jsonapi/request_parser.rb
|
176
177
|
- lib/jsonapi/resource.rb
|
177
178
|
- lib/jsonapi/resource_controller.rb
|