graphiti-rb 1.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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"