fast_schema_dumper 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6d00d757f9b258b9b438f8166e9eb8f20fffbdf85ff3c2ca0f139d3869569f46
4
+ data.tar.gz: e84597beb407fc62ac5a2befcc958de139e749ad450721dab80f85df1b98ed6c
5
+ SHA512:
6
+ metadata.gz: 3caede5f1ffbababb0bcdb98c6c12ca7fe2abaaed60abb9b9cc2faffb125e23303a16b733cd3e5c0049d434397ed13eedda4bd7e0b5266cd349514ff0832267a
7
+ data.tar.gz: 1fb86fe0fed6f402817d9716c7e2180da3618a31a5388888bf9b9762be10671c37046e0908cee3a3c4488d53a9787bced38aa24459948495aea7a1589c27d403
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # FastSchemaDumper
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/fast_schema_dumper`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ ```bash
12
+ bundle add fast_schema_dumper
13
+ ```
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ ```bash
18
+ gem install fast_schema_dumper
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/fast_schema_dumper.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fast_schema_dumper/cli'
4
+ exit FastSchemaDumper::CLI.run(ARGV)
@@ -0,0 +1,33 @@
1
+ require 'erb'
2
+ require 'active_record'
3
+ require 'active_record/database_configurations'
4
+
5
+ require_relative './fast_dumper'
6
+
7
+ module FastSchemaDumper
8
+ class CLI
9
+ def self.run(...)
10
+ new.run(...)
11
+ end
12
+
13
+ def run(argv)
14
+ argv = argv.dup
15
+
16
+ env = ENV['RAILS_ENV'] || 'development'
17
+
18
+ database_yml_path = File.join(Dir.pwd, 'config', 'database.yml')
19
+ database_yml = Psych.safe_load(ERB.new(File.read(database_yml_path)).result, aliases: true)
20
+ config = database_yml[env]
21
+ # Override pool size to 1 for faster startup
22
+ config['pool'] = 1
23
+
24
+ # Prepare the ActiveRecord connection configuration
25
+ hash_config = ActiveRecord::DatabaseConfigurations::HashConfig.new(env, 'primary', config)
26
+ ActiveRecord::Base.establish_connection(hash_config)
27
+
28
+ SchemaDumper.dump
29
+
30
+ return 0
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,464 @@
1
+ require 'json'
2
+
3
+ module FastSchemaDumper
4
+ class SchemaDumper
5
+ class << self
6
+ def dump(pool = ActiveRecord::Base.connection_pool, stream = $stdout, config = ActiveRecord::Base)
7
+ new.dump(pool, stream, config)
8
+ end
9
+ end
10
+
11
+ def dump(pool = ActiveRecord::Base.connection_pool, stream = $stdout, config = ActiveRecord::Base)
12
+ conn = ActiveRecord::Base.connection
13
+
14
+ @output = []
15
+
16
+ # Get all tables (excluding ar_internal_metadata and schema_migrations)
17
+ tables = conn.exec_query("
18
+ SELECT TABLE_NAME
19
+ FROM INFORMATION_SCHEMA.TABLES
20
+ WHERE TABLE_SCHEMA = DATABASE()
21
+ AND TABLE_TYPE = 'BASE TABLE'
22
+ AND TABLE_NAME NOT IN ('ar_internal_metadata', 'schema_migrations')
23
+ ORDER BY TABLE_NAME
24
+ ").map { |row| row['TABLE_NAME'] }
25
+
26
+ # Get all columns
27
+ columns_data = conn.exec_query("
28
+ select
29
+ TABLE_NAME
30
+ , COLUMN_NAME
31
+ , ORDINAL_POSITION
32
+ , COLUMN_DEFAULT
33
+ , IS_NULLABLE
34
+ , DATA_TYPE
35
+ , CHARACTER_MAXIMUM_LENGTH
36
+ , NUMERIC_PRECISION
37
+ , NUMERIC_SCALE
38
+ , COLUMN_TYPE
39
+ , EXTRA
40
+ , COLUMN_COMMENT
41
+ , DATETIME_PRECISION
42
+ , COLLATION_NAME
43
+ from INFORMATION_SCHEMA.COLUMNS
44
+ where
45
+ TABLE_SCHEMA = database()
46
+ order by TABLE_NAME, ORDINAL_POSITION
47
+ ")
48
+
49
+ # Get all indexes
50
+ indexes_data = conn.exec_query("
51
+ SELECT
52
+ s.TABLE_NAME,
53
+ s.INDEX_NAME,
54
+ s.NON_UNIQUE,
55
+ s.COLUMN_NAME,
56
+ s.SEQ_IN_INDEX,
57
+ s.INDEX_COMMENT,
58
+ s.COLLATION
59
+ FROM INFORMATION_SCHEMA.STATISTICS s
60
+ WHERE s.TABLE_SCHEMA = DATABASE()
61
+ ORDER BY s.TABLE_NAME, s.INDEX_NAME, s.SEQ_IN_INDEX
62
+ ")
63
+
64
+ # Aggregate table information
65
+ # Organize indexes by table
66
+ indexes_by_table = indexes_data.each_with_object({}) do |idx, hash|
67
+ hash[idx['TABLE_NAME']] ||= {}
68
+ hash[idx['TABLE_NAME']][idx['INDEX_NAME']] ||= {
69
+ columns: [],
70
+ unique: idx['NON_UNIQUE'] == 0,
71
+ # length
72
+ orders: {},
73
+ # opclass
74
+ # where
75
+ # using
76
+ # include
77
+ # nulls_not_distinct
78
+ # type
79
+ comment: idx['INDEX_COMMENT'],
80
+ # enabled
81
+ }
82
+ hash[idx['TABLE_NAME']][idx['INDEX_NAME']][:columns] << idx['COLUMN_NAME']
83
+ # Track descending order columns (COLLATION = 'D')
84
+ if idx['COLLATION'] == 'D'
85
+ hash[idx['TABLE_NAME']][idx['INDEX_NAME']][:orders][idx['COLUMN_NAME']] = :desc
86
+ end
87
+ end
88
+
89
+ # Get table options
90
+ table_options = conn.exec_query("
91
+ SELECT
92
+ TABLE_NAME,
93
+ TABLE_COLLATION,
94
+ TABLE_COMMENT
95
+ FROM INFORMATION_SCHEMA.TABLES
96
+ WHERE TABLE_SCHEMA = DATABASE()
97
+ AND TABLE_TYPE = 'BASE TABLE'
98
+ ").each_with_object({}) do |row, hash|
99
+ hash[row['TABLE_NAME']] = {
100
+ collation: row['TABLE_COLLATION'],
101
+ comment: row['TABLE_COMMENT'],
102
+ }
103
+ end
104
+
105
+ # Get foreign keys
106
+ foreign_keys_data = conn.exec_query("
107
+ SELECT
108
+ kcu.TABLE_NAME,
109
+ kcu.CONSTRAINT_NAME,
110
+ kcu.COLUMN_NAME,
111
+ kcu.REFERENCED_TABLE_NAME,
112
+ kcu.REFERENCED_COLUMN_NAME,
113
+ rc.DELETE_RULE,
114
+ rc.UPDATE_RULE
115
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
116
+ JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
117
+ ON kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
118
+ AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
119
+ WHERE kcu.TABLE_SCHEMA = DATABASE()
120
+ AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
121
+ ORDER BY kcu.TABLE_NAME, kcu.CONSTRAINT_NAME
122
+ ")
123
+
124
+ # Organize columns by table
125
+ columns_by_table = columns_data.each_with_object({}) do |col, hash|
126
+ hash[col['TABLE_NAME']] ||= []
127
+ hash[col['TABLE_NAME']] << col
128
+ end
129
+
130
+ # Generate schema for each table
131
+ tables.each do |table_name|
132
+ dump_table(
133
+ table_name,
134
+ columns: columns_by_table[table_name] || [],
135
+ indexes: indexes_by_table[table_name] || {},
136
+ options: table_options[table_name]
137
+ )
138
+ @output << ""
139
+ end
140
+
141
+ # Remove trailing empty line
142
+ @output.pop if @output.last == ""
143
+
144
+ @output << ""
145
+
146
+ # Foreign keys
147
+ # ordered by table_name and constraint_name
148
+
149
+ foreign_keys_by_table = foreign_keys_data.each_with_object({}) do |fk, hash|
150
+ hash[fk['TABLE_NAME']] ||= {}
151
+ hash[fk['TABLE_NAME']][fk['CONSTRAINT_NAME']] ||= {
152
+ column: fk['COLUMN_NAME'],
153
+ referenced_table: fk['REFERENCED_TABLE_NAME'],
154
+ referenced_column: fk['REFERENCED_COLUMN_NAME'],
155
+ constraint_name: fk['CONSTRAINT_NAME'],
156
+ }
157
+ end
158
+
159
+ all_foreign_keys = []
160
+ foreign_keys_by_table.each do |table_name, foreign_keys|
161
+ foreign_keys.each do |constraint_name, fk_data|
162
+ all_foreign_keys << {
163
+ table_name: table_name,
164
+ constraint_name: constraint_name,
165
+ fk_data: fk_data,
166
+ }
167
+ end
168
+ end
169
+
170
+ # Sort by table_name first, then by referenced_table name, then by column name
171
+ all_foreign_keys.sort_by { |fk| [fk[:table_name], fk[:fk_data][:referenced_table], fk[:fk_data][:column]] }.each do |fk|
172
+ fk_line = "add_foreign_key \"#{fk[:table_name]}\", \"#{fk[:fk_data][:referenced_table]}\""
173
+
174
+ # Determine if we need column: or name: option
175
+ # Rails tries to infer the column name from the table name
176
+ # For simple cases: "users" -> "user_id"
177
+ # But it also handles more complex cases
178
+
179
+ inferred_column = "#{singularize(fk[:fk_data][:referenced_table])}_id"
180
+
181
+ # Check if column name matches what Rails would infer
182
+ if fk[:fk_data][:column] != inferred_column
183
+ # Column name is custom, need to specify it
184
+ fk_line += ", column: \"#{fk[:fk_data][:column]}\""
185
+ else
186
+ # Column matches default, check if constraint name is custom
187
+ # Rails generates constraint names starting with "fk_rails_"
188
+ if !fk[:fk_data][:constraint_name].start_with?("fk_rails_")
189
+ fk_line += ", name: \"#{fk[:fk_data][:constraint_name]}\""
190
+ end
191
+ end
192
+
193
+ @output << fk_line
194
+ end
195
+
196
+ stream.print @output.join("\n")
197
+ end
198
+
199
+ private
200
+
201
+ def escape_string(str)
202
+ str.gsub("\\", "\\\\\\\\").gsub('"', '\"').gsub("\n", "\\n").gsub("\r", "\\r").gsub("\t", "\\t")
203
+ end
204
+
205
+ def singularize(str)
206
+ # Simple singularization rules
207
+ case str
208
+ when 'news'
209
+ 'news' # news is both singular and plural
210
+ when /ies$/
211
+ str.sub(/ies$/, 'y')
212
+ when /ses$/
213
+ str.sub(/es$/, '')
214
+ when /s$/
215
+ str.sub(/s$/, '')
216
+ else
217
+ str
218
+ end
219
+ end
220
+
221
+ def dump_table(table_name, columns:, indexes:, options:)
222
+ table_def = "create_table \"#{table_name}\""
223
+
224
+ # id (primary key)
225
+ primary_key = indexes.delete('PRIMARY')
226
+ if primary_key && primary_key[:columns].size == 1 && primary_key[:columns].first == 'id'
227
+ id_column = columns.find { |c| c['COLUMN_NAME'] == 'id' }
228
+ if id_column
229
+ id_options = []
230
+
231
+ needs_id_options = false
232
+
233
+ # type
234
+ if id_column['DATA_TYPE'] != 'bigint'
235
+ id_options << "type: :#{id_column['DATA_TYPE']}"
236
+ needs_id_options = true
237
+ end
238
+
239
+ # comment
240
+ if id_column['COLUMN_COMMENT'] && !id_column['COLUMN_COMMENT'].empty?
241
+ id_options << "comment: \"#{escape_string(id_column['COLUMN_COMMENT'])}\""
242
+ needs_id_options = true
243
+ end
244
+
245
+ # unsigned
246
+ if id_column['COLUMN_TYPE'].include?('unsigned')
247
+ id_options << "unsigned: true"
248
+ needs_id_options = true
249
+ end
250
+
251
+ # type
252
+ if needs_id_options && id_column['DATA_TYPE'] == 'bigint'
253
+ id_options.unshift("type: :bigint")
254
+ end
255
+
256
+ table_def += ", id: { #{id_options.join(', ')} }" if needs_id_options
257
+ end
258
+ elsif primary_key.nil? || (primary_key && primary_key[:columns].first != 'id')
259
+ table_def += ", id: false"
260
+ end
261
+
262
+ # charset, collation
263
+ if options && options[:collation]
264
+ charset = options[:collation].split('_').first
265
+ table_def += ", charset: \"#{charset}\""
266
+ table_def += ", collation: \"#{options[:collation]}\""
267
+ end
268
+
269
+ # comment
270
+ if options && options[:comment] && !options[:comment].empty?
271
+ table_def += ", comment: \"#{escape_string(options[:comment])}\""
272
+ end
273
+
274
+ table_def += ", force: :cascade do |t|"
275
+ @output << table_def
276
+
277
+ # columns
278
+ columns.reject { |c| c['COLUMN_NAME'] == 'id' }.each do |column|
279
+ @output << " #{format_column(column)}"
280
+ end
281
+
282
+ # Indexes
283
+ # Rails orders indexes lexicographically by their column arrays
284
+ # Example: ["a", "b"] < ["a"] < ["b", "c"] < ["b"] < ["d"]
285
+ sorted_indexes = indexes.reject { |name, _| name == 'PRIMARY' }.sort_by do |index_name, index_data|
286
+ # Create an array padded with high values for comparison
287
+ # This ensures that missing columns sort after existing ones
288
+ max_cols = indexes.values.map { |data| data[:columns].size }.max || 1
289
+ cols = index_data[:columns].dup
290
+ # Pad with a string that sorts after any real column name
291
+ cols += ["\xFF" * 100] * (max_cols - cols.size)
292
+ cols
293
+ end
294
+
295
+ sorted_indexes.each do |index_name, index_data|
296
+ @output << " #{format_index(index_name, index_data)}"
297
+ end
298
+
299
+ @output << "end"
300
+ end
301
+
302
+ def format_column(column)
303
+ col_def = "t.#{map_column_type(column)} \"#{column['COLUMN_NAME']}\""
304
+
305
+ # limit (varchar, char)
306
+ if ['varchar', 'char'].include?(column['DATA_TYPE']) && column['CHARACTER_MAXIMUM_LENGTH'] &&
307
+ column['CHARACTER_MAXIMUM_LENGTH'] != 255
308
+ col_def += ", limit: #{column['CHARACTER_MAXIMUM_LENGTH']}"
309
+ end
310
+
311
+ # limit (integers)
312
+ case column['DATA_TYPE']
313
+ when 'tinyint'
314
+ # Always add limit: 1 for tinyint unless it's tinyint(1) which is boolean
315
+ col_def += ", limit: 1" unless column['COLUMN_TYPE'] == 'tinyint(1)'
316
+ when 'smallint'
317
+ col_def += ", limit: 2"
318
+ when 'mediumint'
319
+ col_def += ", limit: 3"
320
+ end
321
+
322
+ # size (text)
323
+ if column['DATA_TYPE'] == 'mediumtext'
324
+ col_def += ", size: :medium"
325
+ elsif column['DATA_TYPE'] == 'longtext'
326
+ col_def += ", size: :long"
327
+ end
328
+
329
+ # precision (datetime)
330
+ if column['DATA_TYPE'] == 'datetime' && column['DATETIME_PRECISION']
331
+ precision = column['DATETIME_PRECISION'].to_i
332
+ col_def += ", precision: nil" if precision == 0
333
+ end
334
+
335
+ # precision, scale (decimal)
336
+ if column['DATA_TYPE'] == 'decimal' && column['NUMERIC_PRECISION']
337
+ col_def += ", precision: #{column['NUMERIC_PRECISION']}"
338
+ col_def += ", scale: #{column['NUMERIC_SCALE']}" if column['NUMERIC_SCALE']
339
+ end
340
+
341
+ # default
342
+ if column['COLUMN_DEFAULT']
343
+ default = format_default_value(column['COLUMN_DEFAULT'], column['DATA_TYPE'], column['COLUMN_TYPE'])
344
+ col_def += ", default: #{default}" unless default.nil?
345
+ end
346
+
347
+ # null
348
+ col_def += ", null: false" if column['IS_NULLABLE'] == 'NO'
349
+
350
+ # comment
351
+ if column['COLUMN_COMMENT'] && !column['COLUMN_COMMENT'].empty?
352
+ col_def += ", comment: \"#{escape_string(column['COLUMN_COMMENT'])}\""
353
+ end
354
+
355
+ # unsigned
356
+ if column['COLUMN_TYPE'].include?('unsigned')
357
+ col_def += ", unsigned: true"
358
+ end
359
+
360
+ # collation
361
+ if column['COLLATION_NAME'] && column['DATA_TYPE'] =~ /char|text/
362
+ # Check if it's different from the table's default collation
363
+ # For now, just check if it's utf8mb4_bin which seems to be the special case
364
+ if column['COLLATION_NAME'] == 'utf8mb4_bin'
365
+ col_def += ", collation: \"#{column['COLLATION_NAME']}\""
366
+ end
367
+ end
368
+
369
+ col_def
370
+ end
371
+
372
+ def map_column_type(column)
373
+ # Check for boolean (tinyint(1))
374
+ if column['COLUMN_TYPE'] == 'tinyint(1)'
375
+ return 'boolean'
376
+ end
377
+
378
+ case column['DATA_TYPE']
379
+ when 'varchar', 'char'
380
+ 'string'
381
+ when 'int', 'tinyint', 'smallint', 'mediumint'
382
+ 'integer'
383
+ when 'bigint'
384
+ 'bigint'
385
+ when 'text', 'tinytext', 'mediumtext', 'longtext'
386
+ 'text'
387
+ when 'datetime', 'timestamp'
388
+ 'datetime'
389
+ when 'date'
390
+ 'date'
391
+ when 'time'
392
+ 'time'
393
+ when 'decimal'
394
+ 'decimal'
395
+ when 'float', 'double'
396
+ 'float'
397
+ when 'json'
398
+ 'json'
399
+ when 'binary', 'varbinary'
400
+ 'binary'
401
+ when 'blob', 'tinyblob', 'mediumblob', 'longblob'
402
+ 'binary'
403
+ else
404
+ column['DATA_TYPE']
405
+ end
406
+ end
407
+
408
+ def format_default_value(default, data_type, column_type = nil)
409
+ return nil if default == 'NULL' || default.nil?
410
+
411
+ # Special handling for boolean (tinyint(1))
412
+ if column_type == 'tinyint(1)'
413
+ return default == '1' ? 'true' : 'false'
414
+ end
415
+
416
+ case data_type
417
+ when 'varchar', 'char', 'text'
418
+ "\"#{escape_string(default)}\""
419
+ when 'int', 'tinyint', 'smallint', 'mediumint', 'bigint'
420
+ default
421
+ when 'datetime', 'timestamp'
422
+ return '-> { "CURRENT_TIMESTAMP" }' if default == 'CURRENT_TIMESTAMP'
423
+ "\"#{default}\""
424
+ when 'json'
425
+ default == "'[]'" ? '[]' : '{}'
426
+ else
427
+ default =~ /^'.*'$/ ? "\"#{default[1..-2]}\"" : default
428
+ end
429
+ end
430
+
431
+ def format_index(index_name, index_data)
432
+ idx_def = "t.index "
433
+
434
+ if index_data[:columns].size == 1
435
+ idx_def += "[\"#{index_data[:columns].first}\"]"
436
+ else
437
+ idx_def += "[#{index_data[:columns].map { |c| "\"#{c}\"" }.join(', ')}]"
438
+ end
439
+
440
+ idx_def += ", name: \"#{index_name}\""
441
+ idx_def += ", unique: true" if index_data[:unique]
442
+
443
+ # order
444
+ if index_data[:orders] && !index_data[:orders].empty?
445
+ order_hash = index_data[:columns].each_with_object({}) do |col, hash|
446
+ if index_data[:orders][col]
447
+ hash[col.to_sym] = index_data[:orders][col]
448
+ end
449
+ end
450
+
451
+ unless order_hash.empty?
452
+ idx_def += ", order: { #{order_hash.map { |k, v| "#{k}: :#{v}" }.join(', ')} }"
453
+ end
454
+ end
455
+
456
+ # comment
457
+ if index_data[:comment] && !index_data[:comment].empty?
458
+ idx_def += ", comment: \"#{escape_string(index_data[:comment])}\""
459
+ end
460
+
461
+ idx_def
462
+ end
463
+ end
464
+ end
@@ -0,0 +1,39 @@
1
+ require_relative './fast_dumper'
2
+
3
+ # Loading this file will overwrite `Ridgepole::Dumper.dump`.
4
+
5
+ # This file must be loaded after the ridgepole gem is loaded.
6
+ raise "Ridgepole is not defined. Require ridgepole before loading this file." if !defined?(Ridgepole)
7
+
8
+ module Ridgepole
9
+ class Dumper
10
+ alias_method :original_dump, :dump
11
+
12
+ def dump
13
+ case ENV['FAST_SCHEMA_DUMPER_MODE']
14
+ in 'disabled'
15
+ original_dump
16
+ in 'verify'
17
+ puts "Warning: fast_schema_dumper is enabled in verify mode" unless ENV['FAST_SCHEMA_DUMPER_SUPPRESS_MESSAGE'] == '1'
18
+ original_results = original_dump
19
+ fast_results = fast_dump
20
+ if original_results != fast_results
21
+ File.write("orig.txt", original_results)
22
+ File.write("fast.txt", fast_results)
23
+ raise "Dumped schema do not match between ActiveRecord::SchemaDumper and fast_schema_dumper. This is a fast_schema_dumper bug."
24
+ end
25
+ fast_results
26
+ else
27
+ puts "Warning: fast_schema_dumper is enabled" unless ENV['FAST_SCHEMA_DUMPER_SUPPRESS_MESSAGE'] == '1'
28
+ fast_dump
29
+ end
30
+ end
31
+
32
+ def fast_dump
33
+ s = StringIO.new
34
+ FastSchemaDumper::SchemaDumper.dump(ActiveRecord::Base.connection_pool, s)
35
+ s.rewind
36
+ s.read
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module FastSchemaDumper
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ require_relative "fast_schema_dumper/version"
2
+
3
+ module FastSchemaDumper
4
+ class Error < StandardError; end
5
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fast_schema_dumper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daisuke Aritomo
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ email:
27
+ - osyoyu@osyoyu.com
28
+ executables:
29
+ - fast_schema_dumper
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - Rakefile
35
+ - exe/fast_schema_dumper
36
+ - lib/fast_schema_dumper.rb
37
+ - lib/fast_schema_dumper/cli.rb
38
+ - lib/fast_schema_dumper/fast_dumper.rb
39
+ - lib/fast_schema_dumper/ridgepole.rb
40
+ - lib/fast_schema_dumper/version.rb
41
+ homepage: https://github.com/osyoyu/fast_schema_dumper
42
+ licenses: []
43
+ metadata:
44
+ allowed_push_host: https://rubygems.org
45
+ homepage_uri: https://github.com/osyoyu/fast_schema_dumper
46
+ source_code_uri: https://github.com/osyoyu/fast_schema_dumper
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 3.2.0
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.7.0.dev
62
+ specification_version: 4
63
+ summary: A fast alternative to ActiveRecord::SchemaDumper
64
+ test_files: []