activerecord_views 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/active_record_views/checksum_cache.rb +15 -7
- data/lib/active_record_views/extension.rb +10 -2
- data/lib/active_record_views/registered_view.rb +1 -1
- data/lib/active_record_views/version.rb +1 -1
- data/lib/active_record_views.rb +42 -18
- data/spec/active_record_views_checksum_cache_spec.rb +45 -0
- data/spec/active_record_views_extension_spec.rb +14 -1
- data/spec/active_record_views_spec.rb +52 -6
- data/spec/internal/app/models/erb_test_model.rb +3 -0
- data/spec/internal/app/models/erb_test_model.sql.erb +1 -0
- data/spec/internal/app/models/modified_a.rb +3 -0
- data/spec/internal/app/models/modified_b.rb +3 -0
- data/spec/spec_helper.rb +1 -1
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be3aa1741d6dced3c4afcf32a941d3821ec0eafc
|
4
|
+
data.tar.gz: f29929115236d12395bad521420b03a4f799f382
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c623e3dec891c353d1bb3d2b4a6466a2ea4ea520d5c4082007a3303c358982b6e832d251e9127b37ce0ba1b366cb866547eaa0ae57173fb868d0c4683582e4f
|
7
|
+
data.tar.gz: 7cde80415bc90e545f7cdcc6f987525c7c53050e59a2e26b8a83838f40c0f9e9cbbeae29ad15568e4a4e886a028b3bfce359167453d56f7af0d80a89111e3d3d
|
@@ -6,19 +6,27 @@ module ActiveRecordViews
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def init_state_table!
|
9
|
-
|
10
|
-
|
9
|
+
table_exists = @connection.table_exists?('active_record_views')
|
10
|
+
|
11
|
+
if table_exists && !@connection.column_exists?('active_record_views', 'class_name')
|
12
|
+
@connection.execute 'DROP TABLE active_record_views;'
|
13
|
+
table_exists = false
|
14
|
+
end
|
15
|
+
|
16
|
+
unless table_exists
|
17
|
+
@connection.execute 'CREATE TABLE active_record_views(name text PRIMARY KEY, class_name text NOT NULL UNIQUE, checksum text NOT NULL);'
|
11
18
|
end
|
12
19
|
end
|
13
20
|
|
14
21
|
def get(name)
|
15
|
-
@connection.
|
22
|
+
@connection.select_one("SELECT class_name, checksum FROM active_record_views WHERE name = #{@connection.quote name}").try(:symbolize_keys)
|
16
23
|
end
|
17
24
|
|
18
|
-
def set(name,
|
19
|
-
if
|
20
|
-
|
21
|
-
|
25
|
+
def set(name, data)
|
26
|
+
if data
|
27
|
+
data.assert_valid_keys :class_name, :checksum
|
28
|
+
if @connection.update("UPDATE active_record_views SET class_name = #{@connection.quote data[:class_name]}, checksum = #{@connection.quote data[:checksum]} WHERE name = #{@connection.quote name}") == 0
|
29
|
+
@connection.insert "INSERT INTO active_record_views (name, class_name, checksum) VALUES (#{@connection.quote name}, #{@connection.quote data[:class_name]}, #{@connection.quote data[:checksum]})"
|
22
30
|
end
|
23
31
|
else
|
24
32
|
@connection.delete "DELETE FROM active_record_views WHERE name = #{@connection.quote name}"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
1
3
|
module ActiveRecordViews
|
2
4
|
module Extension
|
3
5
|
extend ActiveSupport::Concern
|
@@ -13,10 +15,16 @@ module ActiveRecordViews
|
|
13
15
|
sql ||= begin
|
14
16
|
sql_path = ActiveRecordViews.find_sql_file(self.name.underscore)
|
15
17
|
ActiveRecordViews.register_for_reload self, sql_path
|
16
|
-
|
18
|
+
|
19
|
+
if sql_path.end_with?('.erb')
|
20
|
+
ERB.new(File.read(sql_path)).result
|
21
|
+
else
|
22
|
+
File.read(sql_path)
|
23
|
+
end
|
17
24
|
end
|
25
|
+
|
18
26
|
unless ActiveRecordViews::Extension.currently_migrating?
|
19
|
-
ActiveRecordViews.create_view self.connection, self.table_name, sql
|
27
|
+
ActiveRecordViews.create_view self.connection, self.table_name, self.name, sql
|
20
28
|
end
|
21
29
|
end
|
22
30
|
end
|
@@ -18,7 +18,7 @@ module ActiveRecordViews
|
|
18
18
|
|
19
19
|
def reload!
|
20
20
|
if File.exists? sql_path
|
21
|
-
ActiveRecordViews.create_view model_class.connection, model_class.table_name, File.read(sql_path)
|
21
|
+
ActiveRecordViews.create_view model_class.connection, model_class.table_name, model_class.name, File.read(sql_path)
|
22
22
|
else
|
23
23
|
ActiveRecordViews.drop_view model_class.connection, model_class.table_name
|
24
24
|
end
|
data/lib/active_record_views.rb
CHANGED
@@ -21,6 +21,8 @@ module ActiveRecordViews
|
|
21
21
|
self.sql_load_path.each do |dir|
|
22
22
|
path = "#{dir}/#{name}.sql"
|
23
23
|
return path if File.exists?(path)
|
24
|
+
path = path + '.erb'
|
25
|
+
return path if File.exists?(path)
|
24
26
|
end
|
25
27
|
raise "could not find #{name}.sql"
|
26
28
|
end
|
@@ -32,27 +34,35 @@ module ActiveRecordViews
|
|
32
34
|
!connection.outside_transaction?
|
33
35
|
end
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
begin
|
38
|
+
recursing = Thread.current[:active_record_views_without_transaction]
|
39
|
+
Thread.current[:active_record_views_without_transaction] = true
|
40
|
+
|
41
|
+
if in_transaction && !recursing
|
42
|
+
begin
|
43
|
+
temp_connection = connection.pool.checkout
|
44
|
+
yield temp_connection
|
45
|
+
ensure
|
46
|
+
connection.pool.checkin temp_connection
|
47
|
+
end
|
48
|
+
else
|
49
|
+
yield connection
|
41
50
|
end
|
42
|
-
|
43
|
-
|
51
|
+
ensure
|
52
|
+
Thread.current[:active_record_views_without_transaction] = nil
|
44
53
|
end
|
45
54
|
end
|
46
55
|
|
47
|
-
def self.create_view(connection, name, sql)
|
56
|
+
def self.create_view(connection, name, class_name, sql)
|
48
57
|
without_transaction connection do |connection|
|
49
58
|
cache = ActiveRecordViews::ChecksumCache.new(connection)
|
50
|
-
|
51
|
-
return if cache.get(name) ==
|
59
|
+
data = {class_name: class_name, checksum: Digest::SHA1.hexdigest(sql)}
|
60
|
+
return if cache.get(name) == data
|
52
61
|
|
53
62
|
begin
|
54
63
|
connection.execute "CREATE OR REPLACE VIEW #{connection.quote_table_name name} AS #{sql}"
|
55
64
|
rescue ActiveRecord::StatementInvalid => original_exception
|
65
|
+
raise unless view_exists?(connection, name)
|
56
66
|
connection.transaction :requires_new => true do
|
57
67
|
without_dependencies connection, name do
|
58
68
|
connection.execute "DROP VIEW #{connection.quote_table_name name}"
|
@@ -61,7 +71,7 @@ module ActiveRecordViews
|
|
61
71
|
end
|
62
72
|
end
|
63
73
|
|
64
|
-
cache.set name,
|
74
|
+
cache.set name, data
|
65
75
|
end
|
66
76
|
end
|
67
77
|
|
@@ -73,6 +83,14 @@ module ActiveRecordViews
|
|
73
83
|
end
|
74
84
|
end
|
75
85
|
|
86
|
+
def self.view_exists?(connection, name)
|
87
|
+
connection.select_value(<<-SQL).present?
|
88
|
+
SELECT 1
|
89
|
+
FROM information_schema.views
|
90
|
+
WHERE table_schema = 'public' AND table_name = #{connection.quote name};
|
91
|
+
SQL
|
92
|
+
end
|
93
|
+
|
76
94
|
def self.get_view_dependencies(connection, name)
|
77
95
|
connection.select_rows <<-SQL
|
78
96
|
WITH RECURSIVE dependants AS (
|
@@ -93,11 +111,12 @@ module ActiveRecordViews
|
|
93
111
|
|
94
112
|
SELECT
|
95
113
|
oid::regclass::text AS name,
|
114
|
+
class_name,
|
96
115
|
pg_catalog.pg_get_viewdef(oid) AS definition
|
97
116
|
FROM dependants
|
98
117
|
INNER JOIN active_record_views ON active_record_views.name = oid::regclass::text
|
99
118
|
WHERE level > 0
|
100
|
-
GROUP BY oid
|
119
|
+
GROUP BY oid, class_name
|
101
120
|
ORDER BY MAX(level)
|
102
121
|
;
|
103
122
|
SQL
|
@@ -106,19 +125,24 @@ module ActiveRecordViews
|
|
106
125
|
def self.without_dependencies(connection, name)
|
107
126
|
dependencies = get_view_dependencies(connection, name)
|
108
127
|
|
109
|
-
dependencies.reverse.each do |name, _|
|
128
|
+
dependencies.reverse.each do |name, _, _|
|
110
129
|
connection.execute "DROP VIEW #{name};"
|
111
130
|
end
|
112
131
|
|
113
132
|
yield
|
114
133
|
|
115
|
-
dependencies.each do |name, definition|
|
116
|
-
|
134
|
+
dependencies.each do |name, class_name, definition|
|
135
|
+
begin
|
136
|
+
class_name.constantize
|
137
|
+
rescue NameError => e
|
138
|
+
raise unless e.missing_name?(class_name)
|
139
|
+
connection.execute "CREATE VIEW #{name} AS #{definition};"
|
140
|
+
end
|
117
141
|
end
|
118
142
|
end
|
119
143
|
|
120
|
-
def self.register_for_reload(
|
121
|
-
self.registered_views << RegisteredView.new(
|
144
|
+
def self.register_for_reload(model_class, sql_path)
|
145
|
+
self.registered_views << RegisteredView.new(model_class, sql_path)
|
122
146
|
end
|
123
147
|
|
124
148
|
def self.reload_stale_views!
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveRecordViews::ChecksumCache do
|
4
|
+
let(:connection) { ActiveRecord::Base.connection }
|
5
|
+
|
6
|
+
describe 'initialisation' do
|
7
|
+
context 'with no existing table' do
|
8
|
+
it 'creates the table' do
|
9
|
+
expect(ActiveRecord::Base.connection).to receive(:execute).with(/\ACREATE TABLE active_record_views/).once.and_call_original
|
10
|
+
|
11
|
+
expect(connection.table_exists?('active_record_views')).to eq false
|
12
|
+
ActiveRecordViews::ChecksumCache.new(connection)
|
13
|
+
expect(connection.table_exists?('active_record_views')).to eq true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with existing table' do
|
18
|
+
before do
|
19
|
+
ActiveRecordViews::ChecksumCache.new(connection)
|
20
|
+
expect(connection.table_exists?('active_record_views')).to eq true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'does not recreate the table' do
|
24
|
+
expect(ActiveRecord::Base.connection).to receive(:execute).never
|
25
|
+
|
26
|
+
ActiveRecordViews::ChecksumCache.new(connection)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with old table' do
|
31
|
+
before do
|
32
|
+
connection.execute 'CREATE TABLE active_record_views(name text PRIMARY KEY, checksum text NOT NULL);'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'recreates the table' do
|
36
|
+
expect(ActiveRecord::Base.connection).to receive(:execute).with(/\ADROP TABLE active_record_views/).once.and_call_original
|
37
|
+
expect(ActiveRecord::Base.connection).to receive(:execute).with(/\ACREATE TABLE active_record_views/).once.and_call_original
|
38
|
+
|
39
|
+
expect(connection.column_exists?('active_record_views', 'class_name')).to eq false
|
40
|
+
ActiveRecordViews::ChecksumCache.new(connection)
|
41
|
+
expect(connection.column_exists?('active_record_views', 'class_name')).to eq true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -17,6 +17,11 @@ describe ActiveRecordViews::Extension do
|
|
17
17
|
expect(Namespace::TestModel.first.name).to eq 'Namespaced SQL file'
|
18
18
|
end
|
19
19
|
|
20
|
+
it 'creates database views from external ERB files' do
|
21
|
+
expect(ActiveRecordViews).to receive(:create_view).once.and_call_original
|
22
|
+
expect(ErbTestModel.first.name).to eq 'ERB file'
|
23
|
+
end
|
24
|
+
|
20
25
|
it 'errors if external SQL file is missing' do
|
21
26
|
expect {
|
22
27
|
MissingFileTestModel
|
@@ -28,6 +33,7 @@ describe ActiveRecordViews::Extension do
|
|
28
33
|
expect(ActiveRecordViews).to receive(:create_view).with(
|
29
34
|
anything,
|
30
35
|
'modified_file_test_models',
|
36
|
+
'ModifiedFileTestModel',
|
31
37
|
sql
|
32
38
|
).once.ordered
|
33
39
|
end
|
@@ -76,11 +82,18 @@ describe ActiveRecordViews::Extension do
|
|
76
82
|
end
|
77
83
|
|
78
84
|
it 'does not create if database view is initially up to date' do
|
79
|
-
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'initial_create_test_models', 'SELECT 42 as id'
|
85
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'initial_create_test_models', 'InitialCreateTestModel', 'SELECT 42 as id'
|
80
86
|
expect(ActiveRecord::Base.connection).to receive(:execute).with(/\ACREATE (?:OR REPLACE )?VIEW/).never
|
81
87
|
class InitialCreateTestModel < ActiveRecord::Base
|
82
88
|
is_view 'SELECT 42 as id'
|
83
89
|
end
|
84
90
|
end
|
91
|
+
|
92
|
+
it 'successfully recreates modified paired views with incompatible changes' do
|
93
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'modified_as', 'ModifiedA', 'SELECT 11 AS old_name;'
|
94
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'modified_bs', 'ModifiedB', 'SELECT old_name FROM modified_as;'
|
95
|
+
|
96
|
+
expect(ModifiedB.first.attributes.except(nil)).to eq('new_name' => 22)
|
97
|
+
end
|
85
98
|
end
|
86
99
|
end
|
@@ -5,7 +5,7 @@ describe ActiveRecordViews do
|
|
5
5
|
let(:connection) { ActiveRecord::Base.connection }
|
6
6
|
|
7
7
|
def create_test_view(sql)
|
8
|
-
ActiveRecordViews.create_view connection, 'test', sql
|
8
|
+
ActiveRecordViews.create_view connection, 'test', 'Test', sql
|
9
9
|
end
|
10
10
|
|
11
11
|
def test_view_sql
|
@@ -30,6 +30,17 @@ describe ActiveRecordViews do
|
|
30
30
|
expect(test_view_sql).to eq 'SELECT 1 AS id;'
|
31
31
|
end
|
32
32
|
|
33
|
+
it 'records checksum and class name' do
|
34
|
+
create_test_view 'select 1 as id'
|
35
|
+
expect(connection.select_all('select * from active_record_views').to_a).to eq [
|
36
|
+
{
|
37
|
+
'name' => 'test',
|
38
|
+
'class_name' => 'Test',
|
39
|
+
'checksum' => Digest::SHA1.hexdigest('select 1 as id')
|
40
|
+
}
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
33
44
|
it 'persists views if transaction rolls back' do
|
34
45
|
expect(test_view_sql).to be_nil
|
35
46
|
connection.transaction :requires_new => true do
|
@@ -39,6 +50,12 @@ describe ActiveRecordViews do
|
|
39
50
|
expect(test_view_sql).to eq 'SELECT 1 AS id;'
|
40
51
|
end
|
41
52
|
|
53
|
+
it 'raises descriptive error if view SQL is invalid' do
|
54
|
+
expect {
|
55
|
+
create_test_view 'select blah'
|
56
|
+
}.to raise_error ActiveRecord::StatementInvalid, /column "blah" does not exist/
|
57
|
+
end
|
58
|
+
|
42
59
|
context 'with existing view' do
|
43
60
|
before do
|
44
61
|
create_test_view 'select 1 as id'
|
@@ -57,11 +74,11 @@ describe ActiveRecordViews do
|
|
57
74
|
|
58
75
|
context 'having dependant views' do
|
59
76
|
before do
|
60
|
-
ActiveRecordViews.create_view connection, 'dependency1', 'SELECT id FROM test;'
|
61
|
-
ActiveRecordViews.create_view connection, 'dependency2a', 'SELECT id, id * 2 AS id2 FROM dependency1;'
|
62
|
-
ActiveRecordViews.create_view connection, 'dependency2b', 'SELECT id, id * 4 AS id4 FROM dependency1;'
|
63
|
-
ActiveRecordViews.create_view connection, 'dependency3', 'SELECT * FROM dependency2b;'
|
64
|
-
ActiveRecordViews.create_view connection, 'dependency4', 'SELECT id FROM dependency1 UNION ALL SELECT id FROM dependency3;'
|
77
|
+
ActiveRecordViews.create_view connection, 'dependency1', 'Dependency1', 'SELECT id FROM test;'
|
78
|
+
ActiveRecordViews.create_view connection, 'dependency2a', 'Dependency2a', 'SELECT id, id * 2 AS id2 FROM dependency1;'
|
79
|
+
ActiveRecordViews.create_view connection, 'dependency2b', 'Dependency2b', 'SELECT id, id * 4 AS id4 FROM dependency1;'
|
80
|
+
ActiveRecordViews.create_view connection, 'dependency3', 'Dependency3', 'SELECT * FROM dependency2b;'
|
81
|
+
ActiveRecordViews.create_view connection, 'dependency4', 'Dependency4', 'SELECT id FROM dependency1 UNION ALL SELECT id FROM dependency3;'
|
65
82
|
end
|
66
83
|
|
67
84
|
after do
|
@@ -118,4 +135,33 @@ describe ActiveRecordViews do
|
|
118
135
|
end
|
119
136
|
end
|
120
137
|
end
|
138
|
+
|
139
|
+
describe '.without_transaction' do
|
140
|
+
let(:original_connection) { ActiveRecord::Base.connection }
|
141
|
+
|
142
|
+
it 'yields original connection if no active transaction' do
|
143
|
+
ActiveRecordViews.without_transaction original_connection do |new_connection|
|
144
|
+
expect(new_connection).to eq original_connection
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'yields a new connection if inside a transaction' do
|
149
|
+
original_connection.transaction do
|
150
|
+
ActiveRecordViews.without_transaction original_connection do |new_connection|
|
151
|
+
expect(new_connection).to_not eq original_connection
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'yields original connection if called recursively' do
|
157
|
+
ActiveRecordViews.without_transaction original_connection do |new_connection_1|
|
158
|
+
expect(new_connection_1).to eq original_connection
|
159
|
+
new_connection_1.transaction do
|
160
|
+
ActiveRecordViews.without_transaction new_connection_1 do |new_connection_2|
|
161
|
+
expect(new_connection_2).to eq new_connection_1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
121
167
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
SELECT 1 AS id, '<%= "ERB" %> file'::text AS name;
|
data/spec/spec_helper.rb
CHANGED
@@ -23,7 +23,7 @@ RSpec.configure do |config|
|
|
23
23
|
WHERE table_schema = 'public';
|
24
24
|
SQL
|
25
25
|
view_names.each do |view_name|
|
26
|
-
connection.execute "DROP VIEW IF EXISTS #{connection.quote_table_name view_name}"
|
26
|
+
connection.execute "DROP VIEW IF EXISTS #{connection.quote_table_name view_name} CASCADE"
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord_views
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Weathered
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -111,12 +111,17 @@ files:
|
|
111
111
|
- lib/active_record_views/registered_view.rb
|
112
112
|
- lib/active_record_views/version.rb
|
113
113
|
- lib/activerecord_views.rb
|
114
|
+
- spec/active_record_views_checksum_cache_spec.rb
|
114
115
|
- spec/active_record_views_extension_spec.rb
|
115
116
|
- spec/active_record_views_spec.rb
|
117
|
+
- spec/internal/app/models/erb_test_model.rb
|
118
|
+
- spec/internal/app/models/erb_test_model.sql.erb
|
116
119
|
- spec/internal/app/models/external_file_test_model.rb
|
117
120
|
- spec/internal/app/models/external_file_test_model.sql
|
118
121
|
- spec/internal/app/models/heredoc_test_model.rb
|
119
122
|
- spec/internal/app/models/missing_file_test_model.rb
|
123
|
+
- spec/internal/app/models/modified_a.rb
|
124
|
+
- spec/internal/app/models/modified_b.rb
|
120
125
|
- spec/internal/app/models/namespace/test_model.rb
|
121
126
|
- spec/internal/app/models/namespace/test_model.sql
|
122
127
|
- spec/internal/config/database.yml
|
@@ -147,12 +152,17 @@ signing_key:
|
|
147
152
|
specification_version: 4
|
148
153
|
summary: Automatic database view creation for ActiveRecord
|
149
154
|
test_files:
|
155
|
+
- spec/active_record_views_checksum_cache_spec.rb
|
150
156
|
- spec/active_record_views_extension_spec.rb
|
151
157
|
- spec/active_record_views_spec.rb
|
158
|
+
- spec/internal/app/models/erb_test_model.rb
|
159
|
+
- spec/internal/app/models/erb_test_model.sql.erb
|
152
160
|
- spec/internal/app/models/external_file_test_model.rb
|
153
161
|
- spec/internal/app/models/external_file_test_model.sql
|
154
162
|
- spec/internal/app/models/heredoc_test_model.rb
|
155
163
|
- spec/internal/app/models/missing_file_test_model.rb
|
164
|
+
- spec/internal/app/models/modified_a.rb
|
165
|
+
- spec/internal/app/models/modified_b.rb
|
156
166
|
- spec/internal/app/models/namespace/test_model.rb
|
157
167
|
- spec/internal/app/models/namespace/test_model.sql
|
158
168
|
- spec/internal/config/database.yml
|