graphiti-rb 1.0.alpha.1

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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +20 -0
  6. data/.yardopts +2 -0
  7. data/Appraisals +11 -0
  8. data/CODE_OF_CONDUCT.md +49 -0
  9. data/Gemfile +12 -0
  10. data/Guardfile +32 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +75 -0
  13. data/Rakefile +15 -0
  14. data/bin/appraisal +17 -0
  15. data/bin/console +14 -0
  16. data/bin/rspec +17 -0
  17. data/bin/setup +8 -0
  18. data/gemfiles/rails_4.gemfile +17 -0
  19. data/gemfiles/rails_5.gemfile +17 -0
  20. data/graphiti.gemspec +34 -0
  21. data/lib/generators/jsonapi/resource_generator.rb +169 -0
  22. data/lib/generators/jsonapi/templates/application_resource.rb.erb +15 -0
  23. data/lib/generators/jsonapi/templates/controller.rb.erb +61 -0
  24. data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +30 -0
  25. data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +20 -0
  26. data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +22 -0
  27. data/lib/generators/jsonapi/templates/resource.rb.erb +11 -0
  28. data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
  29. data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
  30. data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +21 -0
  31. data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +34 -0
  32. data/lib/graphiti-rb.rb +1 -0
  33. data/lib/graphiti.rb +121 -0
  34. data/lib/graphiti/adapters/abstract.rb +516 -0
  35. data/lib/graphiti/adapters/active_record.rb +6 -0
  36. data/lib/graphiti/adapters/active_record/base.rb +249 -0
  37. data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +17 -0
  38. data/lib/graphiti/adapters/active_record/has_many_sideload.rb +17 -0
  39. data/lib/graphiti/adapters/active_record/has_one_sideload.rb +17 -0
  40. data/lib/graphiti/adapters/active_record/inferrence.rb +12 -0
  41. data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +30 -0
  42. data/lib/graphiti/adapters/null.rb +236 -0
  43. data/lib/graphiti/base.rb +70 -0
  44. data/lib/graphiti/configuration.rb +21 -0
  45. data/lib/graphiti/context.rb +16 -0
  46. data/lib/graphiti/deserializer.rb +208 -0
  47. data/lib/graphiti/errors.rb +309 -0
  48. data/lib/graphiti/extensions/boolean_attribute.rb +33 -0
  49. data/lib/graphiti/extensions/extra_attribute.rb +70 -0
  50. data/lib/graphiti/extensions/temp_id.rb +26 -0
  51. data/lib/graphiti/filter_operators.rb +25 -0
  52. data/lib/graphiti/hash_renderer.rb +57 -0
  53. data/lib/graphiti/jsonapi_serializable_ext.rb +50 -0
  54. data/lib/graphiti/query.rb +251 -0
  55. data/lib/graphiti/rails.rb +28 -0
  56. data/lib/graphiti/railtie.rb +74 -0
  57. data/lib/graphiti/renderer.rb +60 -0
  58. data/lib/graphiti/resource.rb +110 -0
  59. data/lib/graphiti/resource/configuration.rb +239 -0
  60. data/lib/graphiti/resource/dsl.rb +138 -0
  61. data/lib/graphiti/resource/interface.rb +32 -0
  62. data/lib/graphiti/resource/polymorphism.rb +68 -0
  63. data/lib/graphiti/resource/sideloading.rb +102 -0
  64. data/lib/graphiti/resource_proxy.rb +127 -0
  65. data/lib/graphiti/responders.rb +19 -0
  66. data/lib/graphiti/runner.rb +25 -0
  67. data/lib/graphiti/scope.rb +98 -0
  68. data/lib/graphiti/scoping/base.rb +99 -0
  69. data/lib/graphiti/scoping/default_filter.rb +58 -0
  70. data/lib/graphiti/scoping/extra_attributes.rb +29 -0
  71. data/lib/graphiti/scoping/filter.rb +93 -0
  72. data/lib/graphiti/scoping/filterable.rb +36 -0
  73. data/lib/graphiti/scoping/paginate.rb +87 -0
  74. data/lib/graphiti/scoping/sort.rb +64 -0
  75. data/lib/graphiti/sideload.rb +281 -0
  76. data/lib/graphiti/sideload/belongs_to.rb +34 -0
  77. data/lib/graphiti/sideload/has_many.rb +16 -0
  78. data/lib/graphiti/sideload/has_one.rb +9 -0
  79. data/lib/graphiti/sideload/many_to_many.rb +24 -0
  80. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +108 -0
  81. data/lib/graphiti/stats/dsl.rb +89 -0
  82. data/lib/graphiti/stats/payload.rb +49 -0
  83. data/lib/graphiti/types.rb +172 -0
  84. data/lib/graphiti/util/attribute_check.rb +88 -0
  85. data/lib/graphiti/util/field_params.rb +16 -0
  86. data/lib/graphiti/util/hash.rb +51 -0
  87. data/lib/graphiti/util/hooks.rb +33 -0
  88. data/lib/graphiti/util/include_params.rb +39 -0
  89. data/lib/graphiti/util/persistence.rb +219 -0
  90. data/lib/graphiti/util/relationship_payload.rb +64 -0
  91. data/lib/graphiti/util/serializer_attributes.rb +97 -0
  92. data/lib/graphiti/util/sideload.rb +33 -0
  93. data/lib/graphiti/util/validation_response.rb +78 -0
  94. data/lib/graphiti/version.rb +3 -0
  95. metadata +317 -0
@@ -0,0 +1,15 @@
1
+ <%- unless omit_comments? -%>
2
+ # ApplicationResource is similar to ApplicationRecord - a base class that
3
+ # holds configuration/methods for subclasses.
4
+ # All Resources should inherit from ApplicationResource.
5
+ # Resource documentation: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html
6
+ <%- end -%>
7
+ class ApplicationResource < Graphiti::Resource
8
+ <%- unless omit_comments? -%>
9
+ # Use the ActiveRecord Adapter for all subclasses.
10
+ # Subclasses can still override this default.
11
+ # More on adapters: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html
12
+ <%- end -%>
13
+ self.abstract_class = true
14
+ self.adapter = Graphiti::Adapters::ActiveRecord::Base.new
15
+ end
@@ -0,0 +1,61 @@
1
+ <% module_namespacing do -%>
2
+ class <%= model_klass.name.pluralize %>Controller < ApplicationController
3
+ <%- if actions?('index') -%>
4
+ def index
5
+ <%= file_name.pluralize %> = <%= resource_klass %>.all(params)
6
+ <%- if responders? -%>
7
+ respond_with(<%= file_name.pluralize %>)
8
+ <%- else -%>
9
+ render jsonapi: <%= file_name.pluralize %>
10
+ <%- end -%>
11
+ end
12
+ <%- end -%>
13
+ <%- if actions?('show') -%>
14
+
15
+ def show
16
+ <%= file_name %> = <%= resource_klass %>.find(params)
17
+ <%- if responders? -%>
18
+ respond_with(<%= file_name %>)
19
+ <%- else -%>
20
+ render jsonapi: <%= file_name %>
21
+ <%- end -%>
22
+ end
23
+ <%- end -%>
24
+ <%- if actions?('create') -%>
25
+
26
+ def create
27
+ <%= file_name %> = <%= resource_klass %>.build(params)
28
+
29
+ if <%= file_name %>.save
30
+ render jsonapi: <%= file_name %>, status: 201
31
+ else
32
+ render jsonapi_errors: <%= file_name %>
33
+ end
34
+ end
35
+ <%- end -%>
36
+ <%- if actions?('update') -%>
37
+
38
+ def update
39
+ <%= file_name %> = <%= resource_klass %>.find(params)
40
+
41
+ if <%= file_name %>.update_attributes
42
+ render jsonapi: <%= file_name %>
43
+ else
44
+ render jsonapi_errors: <%= file_name %>
45
+ end
46
+ end
47
+ <%- end -%>
48
+ <%- if actions?('destroy') -%>
49
+
50
+ def destroy
51
+ <%= file_name %> = <%= resource_klass %>.find(params)
52
+
53
+ if <%= file_name %>.destroy
54
+ render jsonapi: { meta: {} }, status: 200
55
+ else
56
+ render jsonapi_errors: <%= file_name %>
57
+ end
58
+ end
59
+ <%- end -%>
60
+ end
61
+ <% end -%>
@@ -0,0 +1,30 @@
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 'works' do
21
+ expect(<%= resource_klass %>).to receive(:build).and_call_original
22
+ expect {
23
+ make_request
24
+ }.to change { <%= model_klass %>.count }.by(1)
25
+ <%= file_name %> = <%= model_klass %>.last
26
+ # assert on properties
27
+ expect(response.status).to eq(201)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
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(<%= resource_klass %>).to receive(:find).and_call_original
13
+ expect { make_request }.to change { <%= model_klass %>.count }.by(-1)
14
+ expect { <%= file_name %>.reload }
15
+ .to raise_error(ActiveRecord::RecordNotFound)
16
+ expect(response.status).to eq(200)
17
+ expect(json).to eq('meta' => {})
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
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 %>", params: params
8
+ end
9
+
10
+ describe 'basic fetch' do
11
+ let!(:<%= file_name %>1) { create(:<%= file_name %>) }
12
+ let!(:<%= file_name %>2) { create(:<%= file_name %>) }
13
+
14
+ it 'works' do
15
+ expect(<%= resource_klass %>).to receive(:all).and_call_original
16
+ make_request
17
+ expect(response.status).to eq(200)
18
+ expect(d.map(&:jsonapi_type).uniq).to eq(['<%= file_name.pluralize %>'])
19
+ expect(d.map(&:id)).to eq([<%= file_name %>1.id, <%= file_name %>2.id])
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %>Resource < ApplicationResource
3
+ <%- attributes.each do |a| -%>
4
+ <%- if [:created_at, :updated_at].include?(a.name.to_sym) -%>
5
+ attribute :<%= a.name %>, :<%= a.type %>, writable: false
6
+ <%- else -%>
7
+ attribute :<%= a.name %>, :<%= a.type %>
8
+ <%- end -%>
9
+ <%- end -%>
10
+ end
11
+ <% end -%>
@@ -0,0 +1,62 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= resource_klass %>, type: :resource do
4
+ describe 'serialization' do
5
+ let!(:<%= file_name %>) { create(:<%= file_name %>) }
6
+
7
+ it 'works' do
8
+ render
9
+ data = jsonapi_data[0]
10
+ expect(data.id).to eq(<%= file_name %>.id)
11
+ expect(data.jsonapi_type).to eq('<%= file_name.pluralize %>')
12
+ <%- attributes.each do |a| -%>
13
+ <%- if [:created_at, :updated_at].include?(a.name.to_sym) -%>
14
+ expect(data.<%= a.name %>).to eq(datetime(<%= file_name %>.<%= a.name %>))
15
+ <%- else -%>
16
+ expect(data.<%= a.name %>).to eq(<%= file_name %>.<%= a.name %>)
17
+ <%- end -%>
18
+ <%- end -%>
19
+ end
20
+ end
21
+ <%- if actions?('index') -%>
22
+
23
+ describe 'filtering' do
24
+ let!(:<%= file_name %>1) { create(:<%= file_name %>) }
25
+ let!(:<%= file_name %>2) { create(:<%= file_name %>) }
26
+
27
+ context 'by id' do
28
+ before do
29
+ params[:filter] = { id: { eq: <%= file_name %>2.id } }
30
+ end
31
+
32
+ it 'works' do
33
+ render
34
+ expect(d.map(&:id)).to eq([<%= file_name %>2.id])
35
+ end
36
+ end
37
+ end
38
+
39
+ describe 'sorting' do
40
+ context 'by id descending' do
41
+ let!(:<%= file_name %>1) { create(:<%= file_name %>) }
42
+ let!(:<%= file_name %>2) { create(:<%= file_name %>) }
43
+
44
+ before do
45
+ params[:sort] = '-id'
46
+ end
47
+
48
+ it 'works' do
49
+ render
50
+ expect(d.map(&:id)).to eq([
51
+ <%= file_name %>2.id,
52
+ <%= file_name %>1.id
53
+ ])
54
+ end
55
+ end
56
+ end
57
+ <%- end -%>
58
+
59
+ describe 'sideloading' do
60
+ # ... your tests ...
61
+ end
62
+ end
@@ -0,0 +1,63 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= resource_klass %>, type: :resource do
4
+ describe 'creating' do
5
+ let(:payload) do
6
+ {
7
+ data: {
8
+ type: '<%= file_name.pluralize %>',
9
+ attributes: { }
10
+ }
11
+ }
12
+ end
13
+
14
+ let(:instance) do
15
+ <%= resource_klass %>.build(payload)
16
+ end
17
+
18
+ it 'works' do
19
+ expect {
20
+ expect(instance.save).to eq(true)
21
+ }.to change { <%= model_klass %>.count }.by(1)
22
+ end
23
+ end
24
+
25
+ describe 'updating' do
26
+ let!(:<%= file_name %>) { create(:<%= file_name %>) }
27
+
28
+ let(:payload) do
29
+ {
30
+ data: {
31
+ id: <%= file_name %>.id.to_s,
32
+ type: '<%= file_name.pluralize %>',
33
+ attributes: { } # Todo!
34
+ }
35
+ }
36
+ end
37
+
38
+ let(:instance) do
39
+ <%= resource_klass %>.find(payload)
40
+ end
41
+
42
+ xit 'works (add some attributes and enable this spec)' do
43
+ expect {
44
+ expect(instance.update_attributes).to eq(true)
45
+ }.to change { <%= file_name %>.reload.updated_at }
46
+ # and change { instance.foo }.to('bar') <- example
47
+ end
48
+ end
49
+
50
+ describe 'destroying' do
51
+ let!(:<%= file_name %>) { create(:<%= file_name %>) }
52
+
53
+ let(:instance) do
54
+ <%= resource_klass %>.find(id: <%= file_name %>.id)
55
+ end
56
+
57
+ it 'works' do
58
+ expect {
59
+ expect(instance.destroy).to eq(true)
60
+ }.to change { <%= model_klass %>.count }.by(-1)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
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}", params: params
8
+ end
9
+
10
+ describe 'basic fetch' do
11
+ let!(:<%= file_name %>) { create(:<%= file_name %>) }
12
+
13
+ it 'works' do
14
+ expect(<%= resource_klass %>).to receive(:find).and_call_original
15
+ make_request
16
+ expect(response.status).to eq(200)
17
+ expect(d.jsonapi_type).to eq('<%= file_name.pluralize %>')
18
+ expect(d.id).to eq(<%= file_name %>.id)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
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(<%= resource_klass %>).to receive(:find).and_call_original
26
+ expect {
27
+ make_request
28
+ }.to change { <%= file_name %>.reload.attributes }
29
+ expect(response.status).to eq(200)
30
+
31
+ # ... assert updates attributes ...
32
+ end
33
+ end
34
+ end
@@ -0,0 +1 @@
1
+ require 'graphiti'
@@ -0,0 +1,121 @@
1
+ require 'json'
2
+ require 'forwardable'
3
+ require 'active_support/core_ext/string'
4
+ require 'active_support/core_ext/class/attribute'
5
+ require 'active_support/core_ext/hash/conversions' # to_xml
6
+ require 'active_support/concern'
7
+ require 'active_support/time'
8
+
9
+ require 'dry-types'
10
+ require 'jsonapi_errorable'
11
+
12
+ require 'jsonapi/serializable'
13
+
14
+ require "graphiti/version"
15
+ require "graphiti/jsonapi_serializable_ext"
16
+ require "graphiti/configuration"
17
+ require "graphiti/context"
18
+ require "graphiti/errors"
19
+ require "graphiti/types"
20
+ require "graphiti/adapters/abstract"
21
+ require "graphiti/resource/sideloading"
22
+ require "graphiti/resource/configuration"
23
+ require "graphiti/resource/dsl"
24
+ require "graphiti/resource/interface"
25
+ require "graphiti/resource/polymorphism"
26
+ require "graphiti/sideload"
27
+ require "graphiti/sideload/has_many"
28
+ require "graphiti/sideload/belongs_to"
29
+ require "graphiti/sideload/has_one"
30
+ require "graphiti/sideload/many_to_many"
31
+ require "graphiti/sideload/polymorphic_belongs_to"
32
+ require "graphiti/resource"
33
+ require "graphiti/resource_proxy"
34
+ require "graphiti/query"
35
+ require "graphiti/scope"
36
+ require "graphiti/deserializer"
37
+ require "graphiti/renderer"
38
+ require "graphiti/hash_renderer"
39
+ require "graphiti/filter_operators"
40
+ require "graphiti/scoping/base"
41
+ require "graphiti/scoping/sort"
42
+ require "graphiti/scoping/paginate"
43
+ require "graphiti/scoping/extra_attributes"
44
+ require "graphiti/scoping/filterable"
45
+ require "graphiti/scoping/default_filter"
46
+ require "graphiti/scoping/filter"
47
+ require "graphiti/stats/dsl"
48
+ require "graphiti/stats/payload"
49
+ require "graphiti/util/include_params"
50
+ require "graphiti/util/field_params"
51
+ require "graphiti/util/hash"
52
+ require "graphiti/util/relationship_payload"
53
+ require "graphiti/util/persistence"
54
+ require "graphiti/util/validation_response"
55
+ require "graphiti/util/sideload"
56
+ require "graphiti/util/hooks"
57
+ require "graphiti/util/attribute_check"
58
+ require "graphiti/util/serializer_attributes"
59
+
60
+ require 'graphiti/adapters/null'
61
+
62
+ require "graphiti/extensions/extra_attribute"
63
+ require "graphiti/extensions/boolean_attribute"
64
+ require "graphiti/extensions/temp_id"
65
+
66
+ if defined?(ActiveRecord)
67
+ require 'graphiti/adapters/active_record'
68
+ end
69
+
70
+ if defined?(Rails)
71
+ require 'graphiti/railtie'
72
+ require 'graphiti/rails'
73
+ require 'graphiti/responders'
74
+ end
75
+
76
+ module Graphiti
77
+ autoload :Base, 'graphiti/base'
78
+
79
+ def self.included(klass)
80
+ klass.instance_eval do
81
+ include Base
82
+ end
83
+ end
84
+
85
+ # @api private
86
+ def self.context
87
+ Thread.current[:context] ||= {}
88
+ end
89
+
90
+ # @api private
91
+ def self.context=(val)
92
+ Thread.current[:context] = val
93
+ end
94
+
95
+ # @api private
96
+ def self.with_context(obj, namespace = nil)
97
+ begin
98
+ prior = self.context
99
+ self.context = { object: obj, namespace: namespace }
100
+ yield
101
+ ensure
102
+ self.context = prior
103
+ end
104
+ end
105
+
106
+ def self.config
107
+ @config ||= Configuration.new
108
+ end
109
+
110
+ # @example
111
+ # Graphiti.configure do |c|
112
+ # c.raise_on_missing_sideload = false
113
+ # end
114
+ #
115
+ # @see Configuration
116
+ def self.configure
117
+ yield config
118
+ end
119
+ end
120
+
121
+ require "graphiti/runner"