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.
Files changed (73) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -2
  4. data/Rakefile +7 -3
  5. data/jsonapi_compliable.gemspec +7 -3
  6. data/lib/generators/jsonapi/resource_generator.rb +8 -79
  7. data/lib/generators/jsonapi/templates/application_resource.rb.erb +2 -1
  8. data/lib/generators/jsonapi/templates/controller.rb.erb +19 -64
  9. data/lib/generators/jsonapi/templates/resource.rb.erb +5 -47
  10. data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
  11. data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
  12. data/lib/jsonapi_compliable.rb +87 -18
  13. data/lib/jsonapi_compliable/adapters/abstract.rb +202 -45
  14. data/lib/jsonapi_compliable/adapters/active_record.rb +6 -130
  15. data/lib/jsonapi_compliable/adapters/active_record/base.rb +247 -0
  16. data/lib/jsonapi_compliable/adapters/active_record/belongs_to_sideload.rb +17 -0
  17. data/lib/jsonapi_compliable/adapters/active_record/has_many_sideload.rb +17 -0
  18. data/lib/jsonapi_compliable/adapters/active_record/has_one_sideload.rb +17 -0
  19. data/lib/jsonapi_compliable/adapters/active_record/inferrence.rb +12 -0
  20. data/lib/jsonapi_compliable/adapters/active_record/many_to_many_sideload.rb +30 -0
  21. data/lib/jsonapi_compliable/adapters/null.rb +177 -6
  22. data/lib/jsonapi_compliable/base.rb +33 -320
  23. data/lib/jsonapi_compliable/context.rb +16 -0
  24. data/lib/jsonapi_compliable/deserializer.rb +14 -39
  25. data/lib/jsonapi_compliable/errors.rb +227 -24
  26. data/lib/jsonapi_compliable/extensions/extra_attribute.rb +3 -1
  27. data/lib/jsonapi_compliable/filter_operators.rb +25 -0
  28. data/lib/jsonapi_compliable/hash_renderer.rb +57 -0
  29. data/lib/jsonapi_compliable/query.rb +190 -202
  30. data/lib/jsonapi_compliable/rails.rb +12 -6
  31. data/lib/jsonapi_compliable/railtie.rb +64 -0
  32. data/lib/jsonapi_compliable/renderer.rb +60 -0
  33. data/lib/jsonapi_compliable/resource.rb +35 -663
  34. data/lib/jsonapi_compliable/resource/configuration.rb +239 -0
  35. data/lib/jsonapi_compliable/resource/dsl.rb +138 -0
  36. data/lib/jsonapi_compliable/resource/interface.rb +32 -0
  37. data/lib/jsonapi_compliable/resource/polymorphism.rb +68 -0
  38. data/lib/jsonapi_compliable/resource/sideloading.rb +102 -0
  39. data/lib/jsonapi_compliable/resource_proxy.rb +127 -0
  40. data/lib/jsonapi_compliable/responders.rb +19 -0
  41. data/lib/jsonapi_compliable/runner.rb +25 -0
  42. data/lib/jsonapi_compliable/scope.rb +37 -79
  43. data/lib/jsonapi_compliable/scoping/extra_attributes.rb +29 -0
  44. data/lib/jsonapi_compliable/scoping/filter.rb +39 -58
  45. data/lib/jsonapi_compliable/scoping/filterable.rb +9 -14
  46. data/lib/jsonapi_compliable/scoping/paginate.rb +9 -3
  47. data/lib/jsonapi_compliable/scoping/sort.rb +16 -4
  48. data/lib/jsonapi_compliable/sideload.rb +221 -347
  49. data/lib/jsonapi_compliable/sideload/belongs_to.rb +34 -0
  50. data/lib/jsonapi_compliable/sideload/has_many.rb +16 -0
  51. data/lib/jsonapi_compliable/sideload/has_one.rb +9 -0
  52. data/lib/jsonapi_compliable/sideload/many_to_many.rb +24 -0
  53. data/lib/jsonapi_compliable/sideload/polymorphic_belongs_to.rb +108 -0
  54. data/lib/jsonapi_compliable/stats/payload.rb +4 -8
  55. data/lib/jsonapi_compliable/types.rb +172 -0
  56. data/lib/jsonapi_compliable/util/attribute_check.rb +88 -0
  57. data/lib/jsonapi_compliable/util/persistence.rb +29 -7
  58. data/lib/jsonapi_compliable/util/relationship_payload.rb +4 -4
  59. data/lib/jsonapi_compliable/util/render_options.rb +4 -32
  60. data/lib/jsonapi_compliable/util/serializer_attributes.rb +98 -0
  61. data/lib/jsonapi_compliable/util/validation_response.rb +15 -9
  62. data/lib/jsonapi_compliable/version.rb +1 -1
  63. metadata +105 -24
  64. data/lib/generators/jsonapi/field_generator.rb +0 -0
  65. data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +0 -29
  66. data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +0 -20
  67. data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +0 -22
  68. data/lib/generators/jsonapi/templates/payload.rb.erb +0 -39
  69. data/lib/generators/jsonapi/templates/serializer.rb.erb +0 -25
  70. data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +0 -19
  71. data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +0 -33
  72. data/lib/jsonapi_compliable/adapters/active_record_sideloading.rb +0 -152
  73. 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