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

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