brick 1.0.54 → 1.0.57
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/brick/extensions.rb +9 -13
- data/lib/brick/version_number.rb +1 -1
- data/lib/generators/brick/migrations_generator.rb +185 -56
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 930370b67a72b31db99cc8d50afa80d4c3be0d07e727155eb20ac5a7628bb1bb
|
4
|
+
data.tar.gz: f2d7964c79172a438800b3c69c63e62b26a4984357ca5d6c7ff9e2ef72e5308f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03e7ba018c426517060aa6312d14a9f1e0877d82b376782ea46b4b1506433bbe3e9e4d2c3d88aaf3d5fb356c68a129fd975b1718d7b2ac826b241efab3f2cee6
|
7
|
+
data.tar.gz: 645c6b750dd870180e312ffddff1d199bb84e92cdde0309f253ec8c927f3ba5304b84ae95b066c260d0523048c607b28148d1b577a479ec661b94de05248813a
|
data/lib/brick/extensions.rb
CHANGED
@@ -1316,16 +1316,21 @@ module ActiveRecord::ConnectionHandling
|
|
1316
1316
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
1317
1317
|
|
1318
1318
|
is_postgres = nil
|
1319
|
-
schema_sql = 'SELECT NULL AS table_schema;'
|
1320
1319
|
case ActiveRecord::Base.connection.adapter_name
|
1321
1320
|
when 'PostgreSQL'
|
1322
1321
|
is_postgres = true
|
1322
|
+
db_schemas = ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;')
|
1323
|
+
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1324
|
+
row = row.is_a?(String) ? row : row['table_schema']
|
1325
|
+
# Remove any system schemas
|
1326
|
+
s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
|
1327
|
+
end
|
1323
1328
|
if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
|
1324
|
-
(sta = multitenancy[:schema_to_analyse]) != 'public')
|
1329
|
+
(sta = multitenancy[:schema_to_analyse]) != 'public') &&
|
1330
|
+
::Brick.db_schemas.include?(sta)
|
1325
1331
|
::Brick.default_schema = schema = sta
|
1326
1332
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1327
1333
|
end
|
1328
|
-
schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
|
1329
1334
|
when 'Mysql2'
|
1330
1335
|
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
1331
1336
|
when 'SQLite'
|
@@ -1342,16 +1347,7 @@ module ActiveRecord::ConnectionHandling
|
|
1342
1347
|
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
1343
1348
|
end
|
1344
1349
|
|
1345
|
-
|
1346
|
-
db_schemas = db_schemas.to_a
|
1347
|
-
end
|
1348
|
-
unless db_schemas.empty?
|
1349
|
-
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1350
|
-
row = row.is_a?(String) ? row : row['table_schema']
|
1351
|
-
# Remove any system schemas
|
1352
|
-
s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
|
1353
|
-
end
|
1354
|
-
end
|
1350
|
+
::Brick.db_schemas ||= []
|
1355
1351
|
|
1356
1352
|
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1357
1353
|
if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
data/lib/brick/version_number.rb
CHANGED
@@ -5,27 +5,27 @@ require 'rails/generators/active_record'
|
|
5
5
|
require 'fancy_gets'
|
6
6
|
|
7
7
|
module Brick
|
8
|
-
# Auto-generates
|
8
|
+
# Auto-generates migration files
|
9
9
|
class MigrationsGenerator < ::Rails::Generators::Base
|
10
10
|
include FancyGets
|
11
11
|
# include ::Rails::Generators::Migration
|
12
12
|
|
13
|
-
# SQL types
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
'
|
13
|
+
# Many SQL types are the same as their migration data type name:
|
14
|
+
# text, integer, bigint, date, boolean, decimal, float
|
15
|
+
# These however are not:
|
16
|
+
SQL_TYPES = { 'character varying' => 'string',
|
17
|
+
'character' => 'string', # %%% Need to put in "limit: 1"
|
18
|
+
'xml' => 'text',
|
19
|
+
'bytea' => 'binary',
|
20
|
+
'timestamp without time zone' => 'timestamp',
|
21
|
+
'timestamp with time zone' => 'timestamp',
|
22
|
+
'time without time zone' => 'time',
|
23
|
+
'time with time zone' => 'time',
|
24
|
+
'double precision' => 'float', # might work with 'double'
|
25
|
+
'smallint' => 'integer' } # %%% Need to put in "limit: 2"
|
18
26
|
# (Still need to find what "inet" and "json" data types map to.)
|
19
27
|
|
20
|
-
|
21
|
-
# class_option(
|
22
|
-
# :with_changes,
|
23
|
-
# type: :boolean,
|
24
|
-
# default: false,
|
25
|
-
# desc: 'Add IMPORT_TEMPLATE to model'
|
26
|
-
# )
|
27
|
-
|
28
|
-
desc 'Auto-generates migrations for an existing database.'
|
28
|
+
desc 'Auto-generates migration files for an existing database.'
|
29
29
|
|
30
30
|
def brick_migrations
|
31
31
|
# If Apartment is active, see if a default schema to analyse is indicated
|
@@ -38,27 +38,33 @@ module Brick
|
|
38
38
|
return
|
39
39
|
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
if Dir.exist?(mig_path)
|
41
|
+
key_type = (ActiveRecord.version < ::Gem::Version.new('5.1') ? 'integer' : 'bigint')
|
42
|
+
is_4x_rails = ActiveRecord.version < ::Gem::Version.new('5.0')
|
43
|
+
ar_version = "[#{ActiveRecord.version.segments[0..1].join('.')}]" unless is_4x_rails
|
44
|
+
is_insert_versions = true
|
45
|
+
is_delete_versions = false
|
46
|
+
versions_to_delete_or_append = nil
|
47
|
+
if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{::Rails.root}/db/migrate")
|
48
48
|
if Dir["#{mig_path}/**/*.rb"].present?
|
49
49
|
puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
|
50
|
-
mig_path2 = "#{
|
50
|
+
mig_path2 = "#{::Rails.root}/tmp/brick_migrations"
|
51
|
+
is_insert_versions = false unless mig_path == mig_path2
|
51
52
|
if Dir.exist?(mig_path2)
|
52
53
|
if Dir["#{mig_path2}/**/*.rb"].present?
|
53
54
|
puts "As well, temporary folder #{mig_path2} also has ruby files present."
|
54
55
|
puts "Choose a destination -- all existing .rb files will be removed:"
|
55
|
-
mig_path2 = gets_list(list: ['Cancel operation!', "
|
56
|
+
mig_path2 = gets_list(list: ['Cancel operation!', "Append migration files into #{mig_path} anyway", mig_path, mig_path2])
|
56
57
|
return if mig_path2.start_with?('Cancel')
|
57
58
|
|
58
|
-
|
59
|
+
existing_mig_files = Dir["#{mig_path2}/**/*.rb"]
|
60
|
+
if (is_insert_versions = mig_path == mig_path2)
|
61
|
+
versions_to_delete_or_append = existing_mig_files.map { |ver| ver.split('/').last.split('_').first }
|
62
|
+
end
|
63
|
+
if mig_path2.start_with?('Append migration files into ')
|
59
64
|
mig_path2 = mig_path
|
60
65
|
else
|
61
|
-
|
66
|
+
is_delete_versions = true
|
67
|
+
existing_mig_files.each { |rb| File.delete(rb) }
|
62
68
|
end
|
63
69
|
else
|
64
70
|
puts "Using temporary folder #{mig_path2} for created migration files.\n\n"
|
@@ -78,57 +84,160 @@ module Brick
|
|
78
84
|
|
79
85
|
# Generate a list of tables that can be chosen
|
80
86
|
chosen = gets_list(list: tables, chosen: tables.dup)
|
81
|
-
#
|
87
|
+
# Start the timestamps back the same number of minutes from now as expected number of migrations to create
|
82
88
|
current_mig_time = Time.now - chosen.length.minutes
|
83
89
|
done = []
|
84
90
|
fks = {}
|
85
91
|
stuck = {}
|
92
|
+
indexes = {} # Track index names to make sure things are unique
|
93
|
+
built_schemas = {} # Track all built schemas so we can place an appropriate drop_schema command only in the first
|
94
|
+
# migration in which that schema is referenced, thereby allowing rollbacks to function properly.
|
95
|
+
versions_to_create = [] # Resulting versions to be used when updating the schema_migrations table
|
86
96
|
# Start by making migrations for fringe tables (those with no foreign keys).
|
87
97
|
# Continue layer by layer, creating migrations for tables that reference ones already done, until
|
88
98
|
# no more migrations can be created. (At that point hopefully all tables are accounted for.)
|
89
99
|
while (fringe = chosen.reject do |tbl|
|
90
|
-
snags = ::Brick.relations
|
100
|
+
snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
|
91
101
|
v[:is_bt] && !v[:polymorphic] &&
|
92
102
|
tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
|
93
103
|
!done.include?(v[:inverse_table])
|
94
104
|
end
|
95
|
-
stuck[tbl] = snags if snags
|
105
|
+
stuck[tbl] = snags if snags&.present?
|
96
106
|
end).present?
|
97
107
|
fringe.each do |tbl|
|
98
|
-
|
99
|
-
|
108
|
+
next unless (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
|
109
|
+
|
110
|
+
pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ActiveRecord::Base.primary_key].flatten)
|
111
|
+
# In case things aren't as standard
|
112
|
+
if pkey_cols.empty?
|
113
|
+
pkey_cols = if rpk.empty? && relation[:cols][arpk.first]&.first == key_type
|
114
|
+
arpk
|
115
|
+
elsif rpk.first
|
116
|
+
rpk
|
117
|
+
end
|
118
|
+
end
|
119
|
+
schema = if (tbl_parts = tbl.split('.')).length > 1
|
120
|
+
if tbl_parts.first == (::Brick.default_schema || 'public')
|
121
|
+
tbl_parts.shift
|
122
|
+
nil
|
123
|
+
else
|
124
|
+
tbl_parts.first
|
125
|
+
end
|
126
|
+
end
|
100
127
|
# %%% For the moment we're skipping polymorphics
|
101
|
-
fkey_cols =
|
102
|
-
mig = +"class Create#{
|
103
|
-
#
|
104
|
-
#
|
105
|
-
|
128
|
+
fkey_cols = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
|
129
|
+
mig = +"class Create#{(full_table_name = tbl_parts.join('_')).camelize} < ActiveRecord::Migration#{ar_version}\n"
|
130
|
+
# If the primary key is also used as a foreign key, will need to do id: false and then build out
|
131
|
+
# a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
|
132
|
+
# if this one has come in as bigint or integer.
|
133
|
+
pk_is_also_fk = fkey_cols.any? { |assoc| pkey_cols&.first == assoc[:fk] } ? pkey_cols&.first : nil
|
134
|
+
# Support missing primary key (by adding: ,id: false)
|
135
|
+
id_option = if pk_is_also_fk || (pkey_col_first = relation[:cols][pkey_cols&.first]&.first) != key_type
|
136
|
+
if pk_is_also_fk || !pkey_cols&.present?
|
137
|
+
', id: false'
|
138
|
+
else
|
139
|
+
case pkey_col_first
|
140
|
+
when 'integer'
|
141
|
+
', id: :serial'
|
142
|
+
when 'bigint'
|
143
|
+
', id: :bigserial'
|
144
|
+
else
|
145
|
+
", id: :#{SQL_TYPES[pkey_col_first] || pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
|
146
|
+
end +
|
147
|
+
(pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '') +
|
148
|
+
(!is_4x_rails && (comment = relation&.fetch(:description, nil))&.present? ? ", comment: #{comment.inspect}" : '')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
# Find the ActiveRecord class in order to see if the columns have comments
|
152
|
+
unless is_4x_rails
|
153
|
+
klass = begin
|
154
|
+
tbl.tr('.', '/').singularize.camelize.constantize
|
155
|
+
rescue StandardError
|
156
|
+
end
|
157
|
+
if klass
|
158
|
+
unless ActiveRecord::Migration.table_exists?(klass.table_name)
|
159
|
+
puts "WARNING: Unable to locate table #{klass.table_name} (for #{klass.name})."
|
160
|
+
klass = nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
# Refer to this table name as a symbol or dotted string as appropriate
|
165
|
+
tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
|
166
|
+
mig << " def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
|
167
|
+
mig << " create_schema :#{schema} unless reverting? || schema_exists?(:#{schema})\n" if schema
|
168
|
+
mig << " create_table #{tbl_code}#{id_option} do |t|\n"
|
106
169
|
possible_ts = [] # Track possible generic timestamps
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
if
|
112
|
-
|
113
|
-
next
|
170
|
+
add_fks = [] # Track foreign keys to add after table creation
|
171
|
+
relation[:cols].each do |col, col_type|
|
172
|
+
sql_type = SQL_TYPES[col_type.first] || col_type.first
|
173
|
+
suffix = col_type[3] ? +', null: false' : +''
|
174
|
+
if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
|
175
|
+
suffix << ", comment: #{comment.inspect}"
|
114
176
|
end
|
115
|
-
|
116
|
-
sql_type ||= col_type.first
|
117
177
|
# Determine if this column is used as part of a foreign key
|
118
178
|
if fk = fkey_cols.find { |assoc| col == assoc[:fk] }
|
119
|
-
|
179
|
+
to_table = fk[:inverse_table].split('.')
|
180
|
+
to_table = to_table.length == 1 ? ":#{to_table.first}" : "'#{fk[:inverse_table]}'"
|
181
|
+
if fk[:fk] != "#{fk[:assoc_name].singularize}_id" # Need to do our own foreign_key tricks, not use references?
|
182
|
+
column = fk[:fk]
|
183
|
+
mig << " t.#{sql_type} :#{column}#{suffix}\n"
|
184
|
+
add_fks << [to_table, column, ::Brick.relations[fk[:inverse_table]]]
|
185
|
+
else
|
186
|
+
suffix << ", type: :#{sql_type}" unless sql_type == key_type
|
187
|
+
# Will the resulting default index name be longer than what Postgres allows? (63 characters)
|
188
|
+
if (idx_name = ActiveRecord::Base.connection.index_name(tbl, {column: col})).length > 63
|
189
|
+
# Try to find a shorter name that hasn't been used yet
|
190
|
+
unless indexes.key?(shorter = idx_name[0..62]) ||
|
191
|
+
indexes.key?(shorter = idx_name.tr('_', '')[0..62]) ||
|
192
|
+
indexes.key?(shorter = idx_name.tr('aeio', '')[0..62])
|
193
|
+
puts "Unable to easily find unique name for index #{idx_name} that is shorter than 64 characters,"
|
194
|
+
puts "so have resorted to this GUID-based identifier: #{shorter = "#{tbl[0..25]}_#{::SecureRandom.uuid}"}."
|
195
|
+
end
|
196
|
+
suffix << ", index: { name: '#{shorter || idx_name}' }"
|
197
|
+
indexes[shorter || idx_name] = nil
|
198
|
+
end
|
199
|
+
mig << " t.references :#{fk[:assoc_name]}#{suffix}, foreign_key: { to_table: #{to_table} }\n"
|
200
|
+
end
|
120
201
|
else
|
121
|
-
|
202
|
+
next if !id_option&.end_with?('id: false') && pkey_cols.include?(col)
|
203
|
+
|
204
|
+
# See if there are generic timestamps
|
205
|
+
if sql_type == 'timestamp' && ['created_at','updated_at'].include?(col)
|
206
|
+
possible_ts << [col, !col_type[3]]
|
207
|
+
else
|
208
|
+
mig << emit_column(sql_type, col, suffix)
|
209
|
+
end
|
122
210
|
end
|
123
211
|
end
|
124
|
-
if possible_ts.length == 2 # Both created_at and updated_at
|
212
|
+
if possible_ts.length == 2 && # Both created_at and updated_at
|
213
|
+
# Rails 5 and later timestamps default to NOT NULL
|
214
|
+
(possible_ts.first.last == is_4x_rails && possible_ts.last.last == is_4x_rails)
|
125
215
|
mig << "\n t.timestamps\n"
|
126
|
-
else # Just one or the other
|
127
|
-
possible_ts.each { |ts| emit_column('timestamp', ts) }
|
216
|
+
else # Just one or the other, or a nullability mismatch
|
217
|
+
possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
|
218
|
+
end
|
219
|
+
mig << " end\n"
|
220
|
+
mig << " execute('ALTER TABLE #{tbl} ADD PRIMARY KEY (#{pk_is_also_fk});')\n" if pk_is_also_fk
|
221
|
+
add_fks.each do |add_fk|
|
222
|
+
is_commented = false
|
223
|
+
# add_fk[2] holds the inverse relation
|
224
|
+
unless (pk = add_fk[2][:pkey].values.flatten&.first)
|
225
|
+
is_commented = true
|
226
|
+
mig << " # (Unable to create relationship because primary key is missing on table #{add_fk[0]})\n"
|
227
|
+
# No official PK, but if coincidentally there's a column of the same name, take a chance on it
|
228
|
+
pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
|
229
|
+
end
|
230
|
+
# to_table column
|
231
|
+
mig << " #{'# ' if is_commented}add_foreign_key #{tbl_code}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
|
232
|
+
end
|
233
|
+
unless built_schemas.key?(schema)
|
234
|
+
mig << " drop_schema :#{schema} if reverting? && schema_exists?(:#{schema})\n"
|
235
|
+
built_schemas[schema] = nil
|
128
236
|
end
|
129
|
-
mig << "
|
237
|
+
mig << " end\nend\n"
|
130
238
|
current_mig_time += 1.minute
|
131
|
-
|
239
|
+
versions_to_create << (version = current_mig_time.strftime('%Y%m%d%H%M00')).split('_').first
|
240
|
+
File.open("#{mig_path}/#{version}_create_#{full_table_name}.rb", "w") { |f| f.write mig }
|
132
241
|
end
|
133
242
|
done.concat(fringe)
|
134
243
|
chosen -= done
|
@@ -140,21 +249,41 @@ module Brick
|
|
140
249
|
snag.last[:assoc_name]
|
141
250
|
end.join(', ')}"
|
142
251
|
end
|
143
|
-
|
144
|
-
|
252
|
+
if mig_path.start_with?(cur_path = ::Rails.root.to_s)
|
253
|
+
pretty_mig_path = mig_path[cur_path.length..-1]
|
254
|
+
end
|
255
|
+
puts "\n*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
|
145
256
|
if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
|
146
257
|
puts "-----------------------------------------"
|
147
258
|
puts "Unable to create migrations for #{stuck_sorted.length} tables#{
|
148
259
|
". Here's the top 5 blockers" if stuck_sorted.length > 5
|
149
260
|
}:"
|
150
261
|
pp stuck_sorted[0..4]
|
262
|
+
else # Successful, and now we can update the schema_migrations table accordingly
|
263
|
+
unless ActiveRecord::Migration.table_exists?(ActiveRecord::Base.schema_migrations_table_name)
|
264
|
+
ActiveRecord::SchemaMigration.create_table
|
265
|
+
end
|
266
|
+
# Remove to_delete - to_create
|
267
|
+
if ((versions_to_delete_or_append ||= []) - versions_to_create).present? && is_delete_versions
|
268
|
+
ActiveRecord::Base.execute_sql("DELETE FROM #{
|
269
|
+
ActiveRecord::Base.schema_migrations_table_name} WHERE version IN (#{
|
270
|
+
(versions_to_delete_or_append - versions_to_create).map { |vtd| "'#{vtd}'" }.join(', ')}
|
271
|
+
)")
|
272
|
+
end
|
273
|
+
# Add to_create - to_delete
|
274
|
+
if is_insert_versions && ((versions_to_create ||= []) - versions_to_delete_or_append).present?
|
275
|
+
ActiveRecord::Base.execute_sql("INSERT INTO #{
|
276
|
+
ActiveRecord::Base.schema_migrations_table_name} (version) VALUES #{
|
277
|
+
(versions_to_create - versions_to_delete_or_append).map { |vtc| "('#{vtc}')" }.join(', ')
|
278
|
+
}")
|
279
|
+
end
|
151
280
|
end
|
152
281
|
end
|
153
282
|
|
154
283
|
private
|
155
284
|
|
156
|
-
def emit_column(type, name)
|
157
|
-
" t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}\n"
|
285
|
+
def emit_column(type, name, suffix)
|
286
|
+
" t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
|
158
287
|
end
|
159
288
|
end
|
160
289
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.57
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|