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.
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