jsonapi_compliable 0.11.34 → 1.0.alpha.2
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 +5 -5
- data/.ruby-version +1 -1
- data/.travis.yml +1 -2
- data/Rakefile +7 -3
- data/jsonapi_compliable.gemspec +7 -3
- data/lib/generators/jsonapi/resource_generator.rb +8 -79
- data/lib/generators/jsonapi/templates/application_resource.rb.erb +2 -1
- data/lib/generators/jsonapi/templates/controller.rb.erb +19 -64
- data/lib/generators/jsonapi/templates/resource.rb.erb +5 -47
- data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
- data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
- data/lib/jsonapi_compliable.rb +87 -18
- data/lib/jsonapi_compliable/adapters/abstract.rb +202 -45
- data/lib/jsonapi_compliable/adapters/active_record.rb +6 -130
- data/lib/jsonapi_compliable/adapters/active_record/base.rb +247 -0
- data/lib/jsonapi_compliable/adapters/active_record/belongs_to_sideload.rb +17 -0
- data/lib/jsonapi_compliable/adapters/active_record/has_many_sideload.rb +17 -0
- data/lib/jsonapi_compliable/adapters/active_record/has_one_sideload.rb +17 -0
- data/lib/jsonapi_compliable/adapters/active_record/inferrence.rb +12 -0
- data/lib/jsonapi_compliable/adapters/active_record/many_to_many_sideload.rb +30 -0
- data/lib/jsonapi_compliable/adapters/null.rb +177 -6
- data/lib/jsonapi_compliable/base.rb +33 -320
- data/lib/jsonapi_compliable/context.rb +16 -0
- data/lib/jsonapi_compliable/deserializer.rb +14 -39
- data/lib/jsonapi_compliable/errors.rb +227 -24
- data/lib/jsonapi_compliable/extensions/extra_attribute.rb +3 -1
- data/lib/jsonapi_compliable/filter_operators.rb +25 -0
- data/lib/jsonapi_compliable/hash_renderer.rb +57 -0
- data/lib/jsonapi_compliable/query.rb +190 -202
- data/lib/jsonapi_compliable/rails.rb +12 -6
- data/lib/jsonapi_compliable/railtie.rb +64 -0
- data/lib/jsonapi_compliable/renderer.rb +60 -0
- data/lib/jsonapi_compliable/resource.rb +35 -663
- data/lib/jsonapi_compliable/resource/configuration.rb +239 -0
- data/lib/jsonapi_compliable/resource/dsl.rb +138 -0
- data/lib/jsonapi_compliable/resource/interface.rb +32 -0
- data/lib/jsonapi_compliable/resource/polymorphism.rb +68 -0
- data/lib/jsonapi_compliable/resource/sideloading.rb +102 -0
- data/lib/jsonapi_compliable/resource_proxy.rb +127 -0
- data/lib/jsonapi_compliable/responders.rb +19 -0
- data/lib/jsonapi_compliable/runner.rb +25 -0
- data/lib/jsonapi_compliable/scope.rb +37 -79
- data/lib/jsonapi_compliable/scoping/extra_attributes.rb +29 -0
- data/lib/jsonapi_compliable/scoping/filter.rb +39 -58
- data/lib/jsonapi_compliable/scoping/filterable.rb +9 -14
- data/lib/jsonapi_compliable/scoping/paginate.rb +9 -3
- data/lib/jsonapi_compliable/scoping/sort.rb +16 -4
- data/lib/jsonapi_compliable/sideload.rb +221 -347
- data/lib/jsonapi_compliable/sideload/belongs_to.rb +34 -0
- data/lib/jsonapi_compliable/sideload/has_many.rb +16 -0
- data/lib/jsonapi_compliable/sideload/has_one.rb +9 -0
- data/lib/jsonapi_compliable/sideload/many_to_many.rb +24 -0
- data/lib/jsonapi_compliable/sideload/polymorphic_belongs_to.rb +108 -0
- data/lib/jsonapi_compliable/stats/payload.rb +4 -8
- data/lib/jsonapi_compliable/types.rb +172 -0
- data/lib/jsonapi_compliable/util/attribute_check.rb +88 -0
- data/lib/jsonapi_compliable/util/persistence.rb +29 -7
- data/lib/jsonapi_compliable/util/relationship_payload.rb +4 -4
- data/lib/jsonapi_compliable/util/render_options.rb +4 -32
- data/lib/jsonapi_compliable/util/serializer_attributes.rb +98 -0
- data/lib/jsonapi_compliable/util/validation_response.rb +15 -9
- data/lib/jsonapi_compliable/version.rb +1 -1
- metadata +105 -24
- data/lib/generators/jsonapi/field_generator.rb +0 -0
- data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +0 -29
- data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +0 -20
- data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +0 -22
- data/lib/generators/jsonapi/templates/payload.rb.erb +0 -39
- data/lib/generators/jsonapi/templates/serializer.rb.erb +0 -25
- data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +0 -19
- data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +0 -33
- data/lib/jsonapi_compliable/adapters/active_record_sideloading.rb +0 -152
- data/lib/jsonapi_compliable/scoping/extra_fields.rb +0 -58
File without changes
|
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe "<%= type %>#create", type: :request do
|
4
|
-
subject(:make_request) do
|
5
|
-
jsonapi_post "/<%= api_namespace %>/v1/<%= type %>", payload
|
6
|
-
end
|
7
|
-
|
8
|
-
describe 'basic create' do
|
9
|
-
let(:payload) do
|
10
|
-
{
|
11
|
-
data: {
|
12
|
-
type: '<%= type %>',
|
13
|
-
attributes: {
|
14
|
-
# ... your attrs here
|
15
|
-
}
|
16
|
-
}
|
17
|
-
}
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'creates the resource' do
|
21
|
-
expect {
|
22
|
-
make_request
|
23
|
-
}.to change { <%= model_klass %>.count }.by(1)
|
24
|
-
<%= file_name %> = <%= model_klass %>.last
|
25
|
-
|
26
|
-
assert_payload(:<%= file_name %>, <%= file_name %>, json_item)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe "<%= type %>#destroy", type: :request do
|
4
|
-
subject(:make_request) do
|
5
|
-
jsonapi_delete "/<%= api_namespace %>/v1/<%= type %>/#{<%= file_name %>.id}"
|
6
|
-
end
|
7
|
-
|
8
|
-
describe 'basic destroy' do
|
9
|
-
let!(:<%= file_name %>) { create(:<%= file_name %>) }
|
10
|
-
|
11
|
-
it 'updates the resource' do
|
12
|
-
expect {
|
13
|
-
make_request
|
14
|
-
}.to change { <%= model_klass %>.count }.by(-1)
|
15
|
-
|
16
|
-
expect(response.status).to eq(200)
|
17
|
-
expect(json).to eq('meta' => {})
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe "<%= file_name.pluralize %>#index", type: :request do
|
4
|
-
let(:params) { {} }
|
5
|
-
|
6
|
-
subject(:make_request) do
|
7
|
-
jsonapi_get "/<%= api_namespace %>/v1/<%= file_name.pluralize %>",
|
8
|
-
params: params
|
9
|
-
end
|
10
|
-
|
11
|
-
describe 'basic fetch' do
|
12
|
-
let!(:<%= file_name %>1) { create(:<%= file_name %>) }
|
13
|
-
let!(:<%= file_name %>2) { create(:<%= file_name %>) }
|
14
|
-
|
15
|
-
it 'serializes the list correctly' do
|
16
|
-
make_request
|
17
|
-
expect(json_ids(true)).to match_array([<%= file_name %>1.id, <%= file_name %>2.id])
|
18
|
-
assert_payload(:<%= file_name %>, <%= file_name %>1, json_items[0])
|
19
|
-
assert_payload(:<%= file_name %>, <%= file_name %>2, json_items[1])
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
<%- unless omit_comments? -%>
|
2
|
-
# Register a payload to validate against.
|
3
|
-
# Add expected attributes within this block, e.g.:
|
4
|
-
#
|
5
|
-
# key(:name)
|
6
|
-
#
|
7
|
-
# Optionally validate the type as well:
|
8
|
-
#
|
9
|
-
# key(:name, String)
|
10
|
-
#
|
11
|
-
# This will:
|
12
|
-
#
|
13
|
-
# * Compare record.name == json['name']
|
14
|
-
# * Ensure no extra keys are in the json payload
|
15
|
-
# * Ensure no values are nil (unless allow_nil: true is passed)
|
16
|
-
# * Ensures json['name'] is a string
|
17
|
-
#
|
18
|
-
# If you have custom serialization logic and want to compare against
|
19
|
-
# something other than "record.name", pass a block:
|
20
|
-
#
|
21
|
-
# key(:name) { |record| record.name.upcase }
|
22
|
-
#
|
23
|
-
# Or, if this is a one-off for a particular spec, do that customization at
|
24
|
-
# runtime:
|
25
|
-
#
|
26
|
-
# assert_payload(:person, person_record, json_item) do
|
27
|
-
# key(:name) { 'Homer Simpson' }
|
28
|
-
# end
|
29
|
-
#
|
30
|
-
# For more information, see https://jsonapi-suite.github.io/jsonapi_spec_helpers/
|
31
|
-
<%- end -%>
|
32
|
-
JsonapiSpecHelpers::Payload.register(:<%= file_name %>) do
|
33
|
-
<%- attributes.each do |a| -%>
|
34
|
-
<%- type = a.type == :boolean ? [TrueClass, FalseClass] : a.type.to_s.classify -%>
|
35
|
-
<%- type = String if a.type == :text -%>
|
36
|
-
<%- type = Float if a.type == :decimal -%>
|
37
|
-
key(:<%= a.name %>, <%= type %>)
|
38
|
-
<%- end -%>
|
39
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
<%- unless omit_comments? -%>
|
2
|
-
# Serializers define the rendered JSON for a model instance.
|
3
|
-
# We use jsonapi-rb, which is similar to active_model_serializers.
|
4
|
-
<%- end -%>
|
5
|
-
<% module_namespacing do -%>
|
6
|
-
class Serializable<%= class_name %> < JSONAPI::Serializable::Resource
|
7
|
-
type :<%= type %>
|
8
|
-
|
9
|
-
<%- unless omit_comments? -%>
|
10
|
-
# Add attributes here to ensure they get rendered, .e.g.
|
11
|
-
#
|
12
|
-
# attribute :name
|
13
|
-
#
|
14
|
-
# To customize, pass a block and reference the underlying @object
|
15
|
-
# being serialized:
|
16
|
-
#
|
17
|
-
# attribute :name do
|
18
|
-
# @object.name.upcase
|
19
|
-
# end
|
20
|
-
<%- end -%>
|
21
|
-
<%- attributes.each do |a| -%>
|
22
|
-
attribute :<%= a.name %>
|
23
|
-
<%- end -%>
|
24
|
-
end
|
25
|
-
<% end -%>
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe "<%= file_name.pluralize %>#show", type: :request do
|
4
|
-
let(:params) { {} }
|
5
|
-
|
6
|
-
subject(:make_request) do
|
7
|
-
jsonapi_get "/<%= api_namespace %>/v1/<%= file_name.pluralize %>/#{<%= file_name %>.id}",
|
8
|
-
params: params
|
9
|
-
end
|
10
|
-
|
11
|
-
describe 'basic fetch' do
|
12
|
-
let!(:<%= file_name %>) { create(:<%= file_name %>) }
|
13
|
-
|
14
|
-
it 'serializes the resource correctly' do
|
15
|
-
make_request
|
16
|
-
assert_payload(:<%= file_name %>, <%= file_name %>, json_item)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe "<%= type %>#update", type: :request do
|
4
|
-
subject(:make_request) do
|
5
|
-
jsonapi_put "/<%= api_namespace %>/v1/<%= type %>/#{<%= file_name %>.id}", payload
|
6
|
-
end
|
7
|
-
|
8
|
-
describe 'basic update' do
|
9
|
-
let!(:<%= file_name %>) { create(:<%= file_name %>) }
|
10
|
-
|
11
|
-
let(:payload) do
|
12
|
-
{
|
13
|
-
data: {
|
14
|
-
id: <%= file_name %>.id.to_s,
|
15
|
-
type: '<%= type %>',
|
16
|
-
attributes: {
|
17
|
-
# ... your attrs here
|
18
|
-
}
|
19
|
-
}
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
# Replace 'xit' with 'it' after adding attributes
|
24
|
-
xit 'updates the resource' do
|
25
|
-
expect {
|
26
|
-
make_request
|
27
|
-
}.to change { <%= file_name %>.reload.attributes }
|
28
|
-
assert_payload(:<%= file_name %>, <%= file_name %>, json_item)
|
29
|
-
|
30
|
-
# ... assert updates attributes ...
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,152 +0,0 @@
|
|
1
|
-
module JsonapiCompliable
|
2
|
-
module Adapters
|
3
|
-
module ActiveRecordSideloading
|
4
|
-
def has_many(association_name, scope: nil, resource:, foreign_key:, primary_key: :id, &blk)
|
5
|
-
_scope = scope
|
6
|
-
|
7
|
-
allow_sideload association_name, type: :has_many, resource: resource, foreign_key: foreign_key, primary_key: primary_key do
|
8
|
-
scope do |parents|
|
9
|
-
parent_ids = parents.map { |p| p.send(primary_key) }
|
10
|
-
_scope.call.where(foreign_key => parent_ids.uniq.compact)
|
11
|
-
end
|
12
|
-
|
13
|
-
assign do |parents, children|
|
14
|
-
children_hash = children.group_by(&foreign_key)
|
15
|
-
parents.each do |parent|
|
16
|
-
parent.association(association_name).loaded!
|
17
|
-
relevant_children = children_hash[parent.send(primary_key)] || []
|
18
|
-
relevant_children.each do |c|
|
19
|
-
parent.association(association_name).add_to_target(c, :skip_callbacks)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
instance_eval(&blk) if blk
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def belongs_to(association_name, scope: nil, resource:, foreign_key:, primary_key: :id, &blk)
|
29
|
-
_scope = scope
|
30
|
-
|
31
|
-
allow_sideload association_name, type: :belongs_to, resource: resource, foreign_key: foreign_key, primary_key: primary_key do
|
32
|
-
scope do |parents|
|
33
|
-
parent_ids = parents.map { |p| p.send(foreign_key) }
|
34
|
-
_scope.call.where(primary_key => parent_ids.uniq.compact)
|
35
|
-
end
|
36
|
-
|
37
|
-
assign do |parents, children|
|
38
|
-
children_hash = children.index_by(&primary_key)
|
39
|
-
parents.each do |parent|
|
40
|
-
relevant_child = children_hash[parent.send(foreign_key)]
|
41
|
-
parent.send(:"#{association_name}=", relevant_child)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
instance_eval(&blk) if blk
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def has_one(association_name, scope: nil, resource:, foreign_key:, primary_key: :id, &blk)
|
50
|
-
_scope = scope
|
51
|
-
|
52
|
-
allow_sideload association_name, type: :has_one, foreign_key: foreign_key, primary_key: primary_key, resource: resource do
|
53
|
-
scope do |parents|
|
54
|
-
parent_ids = parents.map { |p| p.send(primary_key) }
|
55
|
-
_scope.call.where(foreign_key => parent_ids.uniq.compact)
|
56
|
-
end
|
57
|
-
|
58
|
-
# The 'assigned' code here is to remove all children that do not
|
59
|
-
# get assigned. This is because there is no 'limit(1)' in the query.
|
60
|
-
# If we did 'limit(1)' for the query, it wouldn't work for index
|
61
|
-
# actions (only 1 would come back, when we want one *per result*).
|
62
|
-
#
|
63
|
-
# Instead, avoid pagination in the query, assign only one result, and
|
64
|
-
# remove anything else. This is more or less what AR does.
|
65
|
-
assign do |parents, children|
|
66
|
-
assigned = []
|
67
|
-
children_hash = children.group_by(&foreign_key)
|
68
|
-
parents.each do |parent|
|
69
|
-
parent.association(association_name).loaded!
|
70
|
-
relevant_children = children_hash[parent.send(primary_key)]
|
71
|
-
next unless relevant_children
|
72
|
-
relevant_child = relevant_children.first
|
73
|
-
|
74
|
-
# Use private methods because of Rails bug
|
75
|
-
# https://github.com/rails/rails/issues/32886
|
76
|
-
association = parent.association(association_name)
|
77
|
-
association.send(:set_owner_attributes, relevant_child)
|
78
|
-
association.send(:set_inverse_instance, relevant_child)
|
79
|
-
association.send(:target=, relevant_child)
|
80
|
-
assigned << relevant_child
|
81
|
-
end
|
82
|
-
children.replace(assigned)
|
83
|
-
end
|
84
|
-
|
85
|
-
instance_eval(&blk) if blk
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def has_and_belongs_to_many(association_name, scope: nil, resource:, foreign_key:, primary_key: :id, as: nil, &blk)
|
90
|
-
through = foreign_key.keys.first
|
91
|
-
fk = foreign_key.values.first
|
92
|
-
_scope = scope
|
93
|
-
|
94
|
-
allow_sideload association_name, type: :habtm, foreign_key: foreign_key, primary_key: primary_key, resource: resource do
|
95
|
-
scope do |parents|
|
96
|
-
parent_ids = parents.map { |p| p.send(primary_key) }
|
97
|
-
parent_ids.uniq!
|
98
|
-
parent_ids.compact!
|
99
|
-
|
100
|
-
table_name = parents[0]
|
101
|
-
.class.reflections[through.to_s].klass.table_name
|
102
|
-
|
103
|
-
_scope.call
|
104
|
-
.includes(through)
|
105
|
-
.where(table_name => { fk => parent_ids })
|
106
|
-
.distinct
|
107
|
-
end
|
108
|
-
|
109
|
-
assign do |parents, children|
|
110
|
-
parents.each do |parent|
|
111
|
-
parent.association(association_name).loaded!
|
112
|
-
relevant_children = children.select { |c| c.send(through).any? { |ct| ct.send(fk) == parent.send(primary_key) } }
|
113
|
-
relevant_children.each do |c|
|
114
|
-
parent.association(association_name).add_to_target(c, :skip_callbacks)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
instance_eval(&blk) if blk
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def polymorphic_belongs_to(association_name, group_by:, groups:, &blk)
|
124
|
-
allow_sideload association_name, type: :polymorphic_belongs_to, polymorphic: true do
|
125
|
-
group_by(group_by)
|
126
|
-
|
127
|
-
groups.each_pair do |type, config|
|
128
|
-
primary_key = config[:primary_key] || :id
|
129
|
-
foreign_key = config[:foreign_key]
|
130
|
-
|
131
|
-
allow_sideload type, parent: self, primary_key: primary_key, foreign_key: foreign_key, type: :belongs_to, resource: config[:resource] do
|
132
|
-
scope do |parents|
|
133
|
-
parent_ids = parents.map { |p| p.send(foreign_key) }
|
134
|
-
parent_ids.compact!
|
135
|
-
parent_ids.uniq!
|
136
|
-
config[:scope].call.where(primary_key => parent_ids)
|
137
|
-
end
|
138
|
-
|
139
|
-
assign do |parents, children|
|
140
|
-
parents.each do |parent|
|
141
|
-
parent.send(:"#{association_name}=", children.find { |c| c.send(primary_key) == parent.send(foreign_key) })
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
instance_eval(&blk) if blk
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
module JsonapiCompliable
|
2
|
-
# Apply logic when an extra field is requested. Useful for eager loading
|
3
|
-
# associations used to compute the extra field.
|
4
|
-
#
|
5
|
-
# Given a Resource
|
6
|
-
#
|
7
|
-
# class PersonResource < ApplicationResource
|
8
|
-
# extra_field :net_worth do |scope|
|
9
|
-
# scope.includes(:assets)
|
10
|
-
# end
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# And a corresponding serializer:
|
14
|
-
#
|
15
|
-
# class SerializablePerson < JSONAPI::Serializable::Resource
|
16
|
-
# extra_attribute :net_worth do
|
17
|
-
# @object.assets.sum(&:value)
|
18
|
-
# end
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# When the user requests the extra field 'net_worth':
|
22
|
-
#
|
23
|
-
# GET /people?extra_fields[people]=net_worth
|
24
|
-
#
|
25
|
-
# The +assets+ will be eager loaded and the 'net_worth' attribute
|
26
|
-
# will be present in the response. If this field is not explicitly
|
27
|
-
# requested, none of this logic fires.
|
28
|
-
#
|
29
|
-
# @see Resource.extra_field
|
30
|
-
# @see Extensions::ExtraAttribute
|
31
|
-
class Scoping::ExtraFields < Scoping::Base
|
32
|
-
# Loop through all requested extra fields. If custom scoping
|
33
|
-
# logic is define for that field, run it. Otherwise, do nothing.
|
34
|
-
#
|
35
|
-
# @return the scope object we are chaining/modofying
|
36
|
-
def apply
|
37
|
-
each_extra_field do |callable|
|
38
|
-
@scope = callable.call(@scope, resource.context)
|
39
|
-
end
|
40
|
-
|
41
|
-
@scope
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def each_extra_field
|
47
|
-
resource.extra_fields.each_pair do |name, callable|
|
48
|
-
if extra_fields.include?(name)
|
49
|
-
yield callable
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def extra_fields
|
55
|
-
query_hash[:extra_fields][resource.type] || []
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|