graphiti 1.0.alpha.5 → 1.0.alpha.6

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphiti/api_test_generator.rb +71 -0
  3. data/lib/generators/graphiti/generator_mixin.rb +45 -0
  4. data/lib/generators/graphiti/resource_generator.rb +99 -0
  5. data/lib/generators/graphiti/resource_test_generator.rb +57 -0
  6. data/lib/generators/{jsonapi → graphiti}/templates/application_resource.rb.erb +0 -0
  7. data/lib/generators/{jsonapi → graphiti}/templates/controller.rb.erb +0 -0
  8. data/lib/generators/{jsonapi → graphiti}/templates/create_request_spec.rb.erb +3 -3
  9. data/lib/generators/{jsonapi → graphiti}/templates/destroy_request_spec.rb.erb +5 -5
  10. data/lib/generators/graphiti/templates/index_request_spec.rb.erb +22 -0
  11. data/lib/generators/{jsonapi → graphiti}/templates/resource.rb.erb +0 -0
  12. data/lib/generators/{jsonapi → graphiti}/templates/resource_reads_spec.rb.erb +12 -12
  13. data/lib/generators/{jsonapi → graphiti}/templates/resource_writes_spec.rb.erb +12 -12
  14. data/lib/generators/graphiti/templates/show_request_spec.rb.erb +21 -0
  15. data/lib/generators/{jsonapi → graphiti}/templates/update_request_spec.rb.erb +5 -5
  16. data/lib/graphiti.rb +0 -6
  17. data/lib/graphiti/adapters/abstract.rb +0 -1
  18. data/lib/graphiti/adapters/active_record/inferrence.rb +8 -1
  19. data/lib/graphiti/base.rb +1 -1
  20. data/lib/graphiti/errors.rb +70 -5
  21. data/lib/graphiti/jsonapi_serializable_ext.rb +18 -6
  22. data/lib/graphiti/query.rb +28 -17
  23. data/lib/graphiti/resource.rb +14 -0
  24. data/lib/graphiti/resource/configuration.rb +22 -3
  25. data/lib/graphiti/resource/dsl.rb +17 -2
  26. data/lib/graphiti/resource/interface.rb +10 -8
  27. data/lib/graphiti/resource/links.rb +6 -3
  28. data/lib/graphiti/runner.rb +2 -1
  29. data/lib/graphiti/schema.rb +33 -5
  30. data/lib/graphiti/schema_diff.rb +71 -1
  31. data/lib/graphiti/scope.rb +4 -9
  32. data/lib/graphiti/scoping/base.rb +2 -2
  33. data/lib/graphiti/scoping/filter.rb +5 -0
  34. data/lib/graphiti/scoping/filterable.rb +21 -6
  35. data/lib/graphiti/scoping/paginate.rb +4 -4
  36. data/lib/graphiti/scoping/sort.rb +26 -5
  37. data/lib/graphiti/sideload.rb +13 -22
  38. data/lib/graphiti/sideload/belongs_to.rb +11 -2
  39. data/lib/graphiti/sideload/has_many.rb +1 -1
  40. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +2 -3
  41. data/lib/graphiti/types.rb +1 -1
  42. data/lib/graphiti/util/class.rb +5 -2
  43. data/lib/graphiti/util/persistence.rb +1 -1
  44. data/lib/graphiti/version.rb +1 -1
  45. metadata +16 -13
  46. data/lib/generators/jsonapi/resource_generator.rb +0 -169
  47. data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +0 -22
  48. data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a97ae8d131e23bc5acf8f8a4fcb97e7cdce25fdc
4
- data.tar.gz: 04ae2c8cc7d56ffea5dc0f8a3fec1a7506a5baeb
3
+ metadata.gz: d7c1746bfb06d696a267ff57810838ea0f5da577
4
+ data.tar.gz: 76133042dda4240b704a4a3f2903f1f5297ccc86
5
5
  SHA512:
6
- metadata.gz: 254a66e4e0bd21eef9038bc8e756b2195cf0d4f0b817944e2f5c71ca139c40da3d0c3a99d7d540b92f77b4bdb5a6acade53efce5ffb839f61ca1a6dcfd01d46e
7
- data.tar.gz: 7790a1eb890eeff8bdf7690df749b3841fbff88487d6ef39dc7f3e9679bdabb9854898ad833e9ed8c5427712f14af416ff6dee4d9ba92cecfe34f773997b1adc
6
+ metadata.gz: a2e690b8375aef1392a9d59a6d1e51be0eb177a5ffa8ffd82950baaf7103ba4315bd96a5530155bf214577a853ee250eddf1ec326cb7f8d7bd521e6818d594f3
7
+ data.tar.gz: 08f1879eb8ae822005635f386b4c6b89287d657d771adacfb7b8cc364a2240b36d8e18edd4db68913017b75dcbeac80c0747286c773536b0c00f29ac0e66d05c
@@ -0,0 +1,71 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'generator_mixin'
3
+
4
+ module Graphiti
5
+ class ApiTestGenerator < ::Rails::Generators::Base
6
+ include GeneratorMixin
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ argument :resource, type: :string
11
+ class_option :'actions',
12
+ type: :array,
13
+ default: nil,
14
+ aliases: ['--actions', '-a'],
15
+ desc: 'Array of controller actions, e.g. "index show destroy"'
16
+
17
+ desc 'Generates rspec request specs at spec/api'
18
+ def generate
19
+ generate_api_specs
20
+ end
21
+
22
+ private
23
+
24
+ def var
25
+ dir.singularize
26
+ end
27
+
28
+ def dir
29
+ @resource.gsub('Resource', '').underscore.pluralize
30
+ end
31
+
32
+ def generate_api_specs
33
+ if actions?('index')
34
+ to = "spec/api/v1/#{dir}/index_spec.rb"
35
+ template('index_request_spec.rb.erb', to)
36
+ end
37
+
38
+ if actions?('show')
39
+ to = "spec/api/v1/#{dir}/show_spec.rb"
40
+ template('show_request_spec.rb.erb', to)
41
+ end
42
+
43
+ if actions?('create')
44
+ to = "spec/api/v1/#{dir}/create_spec.rb"
45
+ template('create_request_spec.rb.erb', to)
46
+ end
47
+
48
+ if actions?('update')
49
+ to = "spec/api/v1/#{dir}/update_spec.rb"
50
+ template('update_request_spec.rb.erb', to)
51
+ end
52
+
53
+ if actions?('destroy')
54
+ to = "spec/api/v1/#{dir}/destroy_spec.rb"
55
+ template('destroy_request_spec.rb.erb', to)
56
+ end
57
+ end
58
+
59
+ def resource_class
60
+ @resource.constantize
61
+ end
62
+
63
+ def type
64
+ resource_class.type
65
+ end
66
+
67
+ def model_class
68
+ resource_class.model
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,45 @@
1
+ module Graphiti
2
+ module GeneratorMixin
3
+ def prompt(header: nil, description: nil, default: nil)
4
+ say(set_color("\n#{header}", :magenta, :bold)) if header
5
+ say("\n#{description}") if description
6
+ answer = ask(set_color("\n(default: #{default}):", :magenta, :bold))
7
+ answer = default if answer.blank? && default != 'nil'
8
+ say(set_color("\nGot it!\n", :white, :bold))
9
+ answer
10
+ end
11
+
12
+ def api_namespace
13
+ @api_namespace ||= begin
14
+ ns = graphiti_config['namespace']
15
+
16
+ if ns.blank?
17
+ ns = prompt \
18
+ header: "What is your API namespace?",
19
+ description: "This will be used as a route prefix, e.g. if you want the route '/books_api/v1/authors' your namespace would be 'books_api'",
20
+ default: 'api'
21
+ update_config!('namespace' => ns)
22
+ end
23
+
24
+ ns
25
+ end
26
+ end
27
+
28
+ def actions
29
+ @options['actions'] || %w(index show create update destroy)
30
+ end
31
+
32
+ def actions?(*methods)
33
+ methods.any? { |m| actions.include?(m) }
34
+ end
35
+
36
+ def graphiti_config
37
+ File.exists?('.graphiticfg.yml') ? YAML.load_file('.graphiticfg.yml') : {}
38
+ end
39
+
40
+ def update_config!(attrs)
41
+ config = graphiti_config.merge(attrs)
42
+ File.open('.graphiticfg.yml', 'w') { |f| f.write(config.to_yaml) }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,99 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'generator_mixin'
3
+
4
+ module Graphiti
5
+ class ResourceGenerator < ::Rails::Generators::NamedBase
6
+ include GeneratorMixin
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
11
+
12
+ class_option :'omit-comments',
13
+ type: :boolean,
14
+ default: false,
15
+ aliases: ['--omit-comments', '-c'],
16
+ desc: 'Generate without documentation comments'
17
+ class_option :'actions',
18
+ type: :array,
19
+ default: nil,
20
+ aliases: ['--actions', '-a'],
21
+ desc: 'Array of controller actions to support, e.g. "index show destroy"'
22
+
23
+ desc "This generator creates a resource file at app/resources, as well as corresponding controller/specs/route/etc"
24
+ def generate_all
25
+ unless model_klass
26
+ raise "You must define a #{class_name} model before generating the corresponding resource."
27
+ end
28
+
29
+ generate_controller
30
+ generate_application_resource unless application_resource_defined?
31
+ generate_route
32
+ generate_resource
33
+ generate_resource_specs
34
+ generate_api_specs
35
+ end
36
+
37
+ private
38
+
39
+ def omit_comments?
40
+ @options['omit-comments']
41
+ end
42
+
43
+ def responders?
44
+ defined?(Responders)
45
+ end
46
+
47
+ def generate_controller
48
+ to = File.join('app/controllers', class_path, "#{file_name.pluralize}_controller.rb")
49
+ template('controller.rb.erb', to)
50
+ end
51
+
52
+ def generate_application_resource
53
+ to = File.join('app/resources', class_path, "application_resource.rb")
54
+ template('application_resource.rb.erb', to)
55
+ end
56
+
57
+ def application_resource_defined?
58
+ 'ApplicationResource'.safe_constantize.present?
59
+ end
60
+
61
+ def generate_route
62
+ code = " resources :#{type}"
63
+ code << ", only: [#{actions.map { |a| ":#{a}" }.join(', ')}]" if actions.length < 5
64
+ code << "\n"
65
+ inject_into_file 'config/routes.rb', after: "scope path: '/v1' do\n" do
66
+ code
67
+ end
68
+ end
69
+
70
+ def generate_resource_specs
71
+ opts = {}
72
+ opts[:actions] = @options[:actions] if @options[:actions]
73
+ invoke 'graphiti:resource_test', [resource_klass], opts
74
+ end
75
+
76
+ def generate_api_specs
77
+ opts = {}
78
+ opts[:actions] = @options[:actions] if @options[:actions]
79
+ invoke 'graphiti:api_test', [resource_klass], opts
80
+ end
81
+
82
+ def generate_resource
83
+ to = File.join('app/resources', class_path, "#{file_name}_resource.rb")
84
+ template('resource.rb.erb', to)
85
+ end
86
+
87
+ def model_klass
88
+ class_name.safe_constantize
89
+ end
90
+
91
+ def resource_klass
92
+ "#{model_klass}Resource"
93
+ end
94
+
95
+ def type
96
+ model_klass.name.underscore.pluralize
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,57 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'generator_mixin'
3
+
4
+ module Graphiti
5
+ class ResourceTestGenerator < ::Rails::Generators::Base
6
+ include GeneratorMixin
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ argument :resource, type: :string
11
+ argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
12
+ class_option :'actions',
13
+ type: :array,
14
+ default: nil,
15
+ aliases: ['--actions', '-a'],
16
+ desc: 'Array of controller actions, e.g. "index show destroy"'
17
+
18
+ desc 'Generates rspec request specs at spec/api'
19
+ def generate
20
+ generate_resource_specs
21
+ end
22
+
23
+ private
24
+
25
+ def var
26
+ dir.singularize
27
+ end
28
+
29
+ def dir
30
+ @resource.gsub('Resource', '').underscore.pluralize
31
+ end
32
+
33
+ def generate_resource_specs
34
+ if actions?('create', 'update', 'destroy')
35
+ to = "spec/resources/#{var}/writes_spec.rb.rb"
36
+ template('resource_writes_spec.rb.erb', to)
37
+ end
38
+
39
+ if actions?('index', 'show')
40
+ to = "spec/resources/#{var}/reads_spec.rb.rb"
41
+ template('resource_reads_spec.rb.erb', to)
42
+ end
43
+ end
44
+
45
+ def resource_class
46
+ @resource.constantize
47
+ end
48
+
49
+ def type
50
+ resource_class.type
51
+ end
52
+
53
+ def model_class
54
+ resource_class.model
55
+ end
56
+ end
57
+ end
@@ -18,11 +18,11 @@ RSpec.describe "<%= type %>#create", type: :request do
18
18
  end
19
19
 
20
20
  it 'works' do
21
- expect(<%= resource_klass %>).to receive(:build).and_call_original
21
+ expect(<%= resource_class %>).to receive(:build).and_call_original
22
22
  expect {
23
23
  make_request
24
- }.to change { <%= model_klass %>.count }.by(1)
25
- <%= file_name %> = <%= model_klass %>.last
24
+ }.to change { <%= model_class %>.count }.by(1)
25
+ <%= var %> = <%= model_class %>.last
26
26
  # assert on properties
27
27
  expect(response.status).to eq(201)
28
28
  end
@@ -2,16 +2,16 @@ require 'rails_helper'
2
2
 
3
3
  RSpec.describe "<%= type %>#destroy", type: :request do
4
4
  subject(:make_request) do
5
- jsonapi_delete "/<%= api_namespace %>/v1/<%= type %>/#{<%= file_name %>.id}"
5
+ jsonapi_delete "/<%= api_namespace %>/v1/<%= type %>/#{<%= var %>.id}"
6
6
  end
7
7
 
8
8
  describe 'basic destroy' do
9
- let!(:<%= file_name %>) { create(:<%= file_name %>) }
9
+ let!(:<%= var %>) { create(:<%= var %>) }
10
10
 
11
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 }
12
+ expect(<%= resource_class %>).to receive(:find).and_call_original
13
+ expect { make_request }.to change { <%= model_class %>.count }.by(-1)
14
+ expect { <%= var %>.reload }
15
15
  .to raise_error(ActiveRecord::RecordNotFound)
16
16
  expect(response.status).to eq(200)
17
17
  expect(json).to eq('meta' => {})
@@ -0,0 +1,22 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe "<%= type %>#index", type: :request do
4
+ let(:params) { {} }
5
+
6
+ subject(:make_request) do
7
+ jsonapi_get "/<%= api_namespace %>/v1/<%= type %>", params: params
8
+ end
9
+
10
+ describe 'basic fetch' do
11
+ let!(:<%= var %>1) { create(:<%= var %>) }
12
+ let!(:<%= var %>2) { create(:<%= var %>) }
13
+
14
+ it 'works' do
15
+ expect(<%= resource_class %>).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(['<%= type %>'])
19
+ expect(d.map(&:id)).to match_array([<%= var %>1.id, <%= var %>2.id])
20
+ end
21
+ end
22
+ end
@@ -1,14 +1,14 @@
1
1
  require 'rails_helper'
2
2
 
3
- RSpec.describe <%= resource_klass %>, type: :resource do
3
+ RSpec.describe <%= resource_class %>, type: :resource do
4
4
  describe 'serialization' do
5
- let!(:<%= file_name %>) { create(:<%= file_name %>) }
5
+ let!(:<%= var %>) { create(:<%= var %>) }
6
6
 
7
7
  it 'works' do
8
8
  render
9
9
  data = jsonapi_data[0]
10
- expect(data.id).to eq(<%= file_name %>.id)
11
- expect(data.jsonapi_type).to eq('<%= file_name.pluralize %>')
10
+ expect(data.id).to eq(<%= var %>.id)
11
+ expect(data.jsonapi_type).to eq('<%= type %>')
12
12
  <%- attributes.each do |a| -%>
13
13
  <%- if [:created_at, :updated_at].include?(a.name.to_sym) -%>
14
14
  expect(data.<%= a.name %>).to eq(datetime(<%= file_name %>.<%= a.name %>))
@@ -21,25 +21,25 @@ RSpec.describe <%= resource_klass %>, type: :resource do
21
21
  <%- if actions?('index') -%>
22
22
 
23
23
  describe 'filtering' do
24
- let!(:<%= file_name %>1) { create(:<%= file_name %>) }
25
- let!(:<%= file_name %>2) { create(:<%= file_name %>) }
24
+ let!(:<%= var %>1) { create(:<%= var %>) }
25
+ let!(:<%= var %>2) { create(:<%= var %>) }
26
26
 
27
27
  context 'by id' do
28
28
  before do
29
- params[:filter] = { id: { eq: <%= file_name %>2.id } }
29
+ params[:filter] = { id: { eq: <%= var %>2.id } }
30
30
  end
31
31
 
32
32
  it 'works' do
33
33
  render
34
- expect(d.map(&:id)).to eq([<%= file_name %>2.id])
34
+ expect(d.map(&:id)).to eq([<%= var %>2.id])
35
35
  end
36
36
  end
37
37
  end
38
38
 
39
39
  describe 'sorting' do
40
40
  context 'by id descending' do
41
- let!(:<%= file_name %>1) { create(:<%= file_name %>) }
42
- let!(:<%= file_name %>2) { create(:<%= file_name %>) }
41
+ let!(:<%= var %>1) { create(:<%= var %>) }
42
+ let!(:<%= var %>2) { create(:<%= var %>) }
43
43
 
44
44
  before do
45
45
  params[:sort] = '-id'
@@ -48,8 +48,8 @@ RSpec.describe <%= resource_klass %>, type: :resource do
48
48
  it 'works' do
49
49
  render
50
50
  expect(d.map(&:id)).to eq([
51
- <%= file_name %>2.id,
52
- <%= file_name %>1.id
51
+ <%= var %>2.id,
52
+ <%= var %>1.id
53
53
  ])
54
54
  end
55
55
  end
@@ -1,63 +1,63 @@
1
1
  require 'rails_helper'
2
2
 
3
- RSpec.describe <%= resource_klass %>, type: :resource do
3
+ RSpec.describe <%= resource_class %>, type: :resource do
4
4
  describe 'creating' do
5
5
  let(:payload) do
6
6
  {
7
7
  data: {
8
- type: '<%= file_name.pluralize %>',
8
+ type: '<%= type %>',
9
9
  attributes: { }
10
10
  }
11
11
  }
12
12
  end
13
13
 
14
14
  let(:instance) do
15
- <%= resource_klass %>.build(payload)
15
+ <%= resource_class %>.build(payload)
16
16
  end
17
17
 
18
18
  it 'works' do
19
19
  expect {
20
20
  expect(instance.save).to eq(true)
21
- }.to change { <%= model_klass %>.count }.by(1)
21
+ }.to change { <%= model_class %>.count }.by(1)
22
22
  end
23
23
  end
24
24
 
25
25
  describe 'updating' do
26
- let!(:<%= file_name %>) { create(:<%= file_name %>) }
26
+ let!(:<%= var %>) { create(:<%= var %>) }
27
27
 
28
28
  let(:payload) do
29
29
  {
30
30
  data: {
31
- id: <%= file_name %>.id.to_s,
32
- type: '<%= file_name.pluralize %>',
31
+ id: <%= var %>.id.to_s,
32
+ type: '<%= type %>',
33
33
  attributes: { } # Todo!
34
34
  }
35
35
  }
36
36
  end
37
37
 
38
38
  let(:instance) do
39
- <%= resource_klass %>.find(payload)
39
+ <%= resource_class %>.find(payload)
40
40
  end
41
41
 
42
42
  xit 'works (add some attributes and enable this spec)' do
43
43
  expect {
44
44
  expect(instance.update_attributes).to eq(true)
45
- }.to change { <%= file_name %>.reload.updated_at }
45
+ }.to change { <%= var %>.reload.updated_at }
46
46
  # and change { instance.foo }.to('bar') <- example
47
47
  end
48
48
  end
49
49
 
50
50
  describe 'destroying' do
51
- let!(:<%= file_name %>) { create(:<%= file_name %>) }
51
+ let!(:<%= var %>) { create(:<%= var %>) }
52
52
 
53
53
  let(:instance) do
54
- <%= resource_klass %>.find(id: <%= file_name %>.id)
54
+ <%= resource_class %>.find(id: <%= var %>.id)
55
55
  end
56
56
 
57
57
  it 'works' do
58
58
  expect {
59
59
  expect(instance.destroy).to eq(true)
60
- }.to change { <%= model_klass %>.count }.by(-1)
60
+ }.to change { <%= model_class %>.count }.by(-1)
61
61
  end
62
62
  end
63
63
  end