jsonapi_compliable 0.11.34 → 1.0.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|