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.
- checksums.yaml +4 -4
- data/lib/generators/graphiti/api_test_generator.rb +71 -0
- data/lib/generators/graphiti/generator_mixin.rb +45 -0
- data/lib/generators/graphiti/resource_generator.rb +99 -0
- data/lib/generators/graphiti/resource_test_generator.rb +57 -0
- data/lib/generators/{jsonapi → graphiti}/templates/application_resource.rb.erb +0 -0
- data/lib/generators/{jsonapi → graphiti}/templates/controller.rb.erb +0 -0
- data/lib/generators/{jsonapi → graphiti}/templates/create_request_spec.rb.erb +3 -3
- data/lib/generators/{jsonapi → graphiti}/templates/destroy_request_spec.rb.erb +5 -5
- data/lib/generators/graphiti/templates/index_request_spec.rb.erb +22 -0
- data/lib/generators/{jsonapi → graphiti}/templates/resource.rb.erb +0 -0
- data/lib/generators/{jsonapi → graphiti}/templates/resource_reads_spec.rb.erb +12 -12
- data/lib/generators/{jsonapi → graphiti}/templates/resource_writes_spec.rb.erb +12 -12
- data/lib/generators/graphiti/templates/show_request_spec.rb.erb +21 -0
- data/lib/generators/{jsonapi → graphiti}/templates/update_request_spec.rb.erb +5 -5
- data/lib/graphiti.rb +0 -6
- data/lib/graphiti/adapters/abstract.rb +0 -1
- data/lib/graphiti/adapters/active_record/inferrence.rb +8 -1
- data/lib/graphiti/base.rb +1 -1
- data/lib/graphiti/errors.rb +70 -5
- data/lib/graphiti/jsonapi_serializable_ext.rb +18 -6
- data/lib/graphiti/query.rb +28 -17
- data/lib/graphiti/resource.rb +14 -0
- data/lib/graphiti/resource/configuration.rb +22 -3
- data/lib/graphiti/resource/dsl.rb +17 -2
- data/lib/graphiti/resource/interface.rb +10 -8
- data/lib/graphiti/resource/links.rb +6 -3
- data/lib/graphiti/runner.rb +2 -1
- data/lib/graphiti/schema.rb +33 -5
- data/lib/graphiti/schema_diff.rb +71 -1
- data/lib/graphiti/scope.rb +4 -9
- data/lib/graphiti/scoping/base.rb +2 -2
- data/lib/graphiti/scoping/filter.rb +5 -0
- data/lib/graphiti/scoping/filterable.rb +21 -6
- data/lib/graphiti/scoping/paginate.rb +4 -4
- data/lib/graphiti/scoping/sort.rb +26 -5
- data/lib/graphiti/sideload.rb +13 -22
- data/lib/graphiti/sideload/belongs_to.rb +11 -2
- data/lib/graphiti/sideload/has_many.rb +1 -1
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +2 -3
- data/lib/graphiti/types.rb +1 -1
- data/lib/graphiti/util/class.rb +5 -2
- data/lib/graphiti/util/persistence.rb +1 -1
- data/lib/graphiti/version.rb +1 -1
- metadata +16 -13
- data/lib/generators/jsonapi/resource_generator.rb +0 -169
- data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +0 -22
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7c1746bfb06d696a267ff57810838ea0f5da577
|
4
|
+
data.tar.gz: 76133042dda4240b704a4a3f2903f1f5297ccc86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
File without changes
|
File without changes
|
@@ -18,11 +18,11 @@ RSpec.describe "<%= type %>#create", type: :request do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'works' do
|
21
|
-
expect(<%=
|
21
|
+
expect(<%= resource_class %>).to receive(:build).and_call_original
|
22
22
|
expect {
|
23
23
|
make_request
|
24
|
-
}.to change { <%=
|
25
|
-
<%=
|
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 %>/#{<%=
|
5
|
+
jsonapi_delete "/<%= api_namespace %>/v1/<%= type %>/#{<%= var %>.id}"
|
6
6
|
end
|
7
7
|
|
8
8
|
describe 'basic destroy' do
|
9
|
-
let!(:<%=
|
9
|
+
let!(:<%= var %>) { create(:<%= var %>) }
|
10
10
|
|
11
11
|
it 'updates the resource' do
|
12
|
-
expect(<%=
|
13
|
-
expect { make_request }.to change { <%=
|
14
|
-
expect { <%=
|
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
|
File without changes
|
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'rails_helper'
|
2
2
|
|
3
|
-
RSpec.describe <%=
|
3
|
+
RSpec.describe <%= resource_class %>, type: :resource do
|
4
4
|
describe 'serialization' do
|
5
|
-
let!(:<%=
|
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(<%=
|
11
|
-
expect(data.jsonapi_type).to eq('<%=
|
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!(:<%=
|
25
|
-
let!(:<%=
|
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: <%=
|
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([<%=
|
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!(:<%=
|
42
|
-
let!(:<%=
|
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
|
-
<%=
|
52
|
-
<%=
|
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 <%=
|
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: '<%=
|
8
|
+
type: '<%= type %>',
|
9
9
|
attributes: { }
|
10
10
|
}
|
11
11
|
}
|
12
12
|
end
|
13
13
|
|
14
14
|
let(:instance) do
|
15
|
-
<%=
|
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 { <%=
|
21
|
+
}.to change { <%= model_class %>.count }.by(1)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
describe 'updating' do
|
26
|
-
let!(:<%=
|
26
|
+
let!(:<%= var %>) { create(:<%= var %>) }
|
27
27
|
|
28
28
|
let(:payload) do
|
29
29
|
{
|
30
30
|
data: {
|
31
|
-
id: <%=
|
32
|
-
type: '<%=
|
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
|
-
<%=
|
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 { <%=
|
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!(:<%=
|
51
|
+
let!(:<%= var %>) { create(:<%= var %>) }
|
52
52
|
|
53
53
|
let(:instance) do
|
54
|
-
<%=
|
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 { <%=
|
60
|
+
}.to change { <%= model_class %>.count }.by(-1)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|