hairtrigger 1.0.0 → 1.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d99ce209cb3572d2bcc2987758e5887b203f3fdf50ef295b18169a9f030d0d3a
4
- data.tar.gz: 8e79c7bf238ebb1339bdefce635eb47050d509faad270c4af88d39a7ba89a8a3
3
+ metadata.gz: 00e0ef28f55f9f28dddc16dadc2acfadbefa3007eacc294591fc8a2047f547ed
4
+ data.tar.gz: 7c1aef527b1343d68651bddc00a4cd1b9012b78a4c9a81c0fd333be8f2adb72d
5
5
  SHA512:
6
- metadata.gz: ffee9a6cac525abb8d0d7e49ec3f78877b54cdbfcaa77a2e2c5b6ac86d4b87401e02f0b54da8a62ebd8029d35ad7d0e798cdbcae34c9db429a4dea5d05aa8e51
7
- data.tar.gz: d0c6a189591d1e0722fcbd72b51392efacd222734ae7da513f2f8da651f9c9ed5cf035174d6af04cb1206913873329cf3d8ec645d722810df86889030586ab96
6
+ metadata.gz: e81ed119bd7f7a3c20fc5f9a7ed365ed5a67551b11063efbd3aeecf9f4116b88b662d6ecf309e7e8fa1b7efedd76519c2f5770851797639750078e034793e152
7
+ data.tar.gz: e1416ac1fca775b6a600c2a658a1aa51088893293a818d56d8bd0df13821ff4bf54d104177db54bf532a85c054cbbde55a1afe2575b1ab48ef66139b06981afa
data/README.md CHANGED
@@ -125,6 +125,21 @@ trigger.after(:insert).declare("user_type text; status text") do
125
125
  end
126
126
  ```
127
127
 
128
+ #### new_as(name) or old_as(name)
129
+ PostgreSQL-specific option for "after" triggers to allow accessing the row as it was before the operation (`old`) or as it is after the operation (`new`). This is useful in statement trigger when you want to compare the old and new values of all rows changed during an update trigger. For example:
130
+
131
+ ```ruby
132
+ trigger.after(:update).for_each(:statement).new_as(:new_users).old_as(:old_users) do
133
+ <<-SQL
134
+ INSERT INTO user_changes(id, old_name, new_name) FROM (
135
+ SELECT new_users.id, old_users.name AS old_name, new_users.name AS new_name
136
+ FROM new_users
137
+ INNER JOIN old_users ON new_users.id = old_users.id
138
+ ) agg
139
+ SQL
140
+ end
141
+ ```
142
+
128
143
  #### all
129
144
  Noop, useful for trigger groups (see below).
130
145
 
@@ -202,6 +217,15 @@ you need to actually implement `up`/`down` methods in your migration
202
217
  (rather than `change`) so that it does the right thing when
203
218
  rolling back.
204
219
 
220
+ The `drop_trigger` currently only supports the `drop_trigger(name, table, options = {})`
221
+ format. You will need to determine what the resulting trigger name is (e.g. `SHOW TRIGGERS`
222
+ query) and use that name in the `drop_triggers` call. Your `down` migration method
223
+ might contain something like:
224
+
225
+ ```ruby
226
+ drop_trigger(:users_after_insert_row_tr, :transactions)
227
+ ```
228
+
205
229
  #### Manual triggers and :compatibility
206
230
 
207
231
  As bugs are fixed and features are implemented in HairTrigger, it's possible
@@ -314,6 +338,13 @@ ignore. Note that this behavior [may change](https://github.com/jenseng/hair_tri
314
338
  in a future release, meaning you'll first need to explicitly drop the
315
339
  existing trigger if you wish to redefine it.
316
340
 
341
+ ## Custom adapters
342
+
343
+ Out of the box, HairTrigger supports the standard adapters for PostgreSQL, MySQL, and SQLite, plus several common variations (`postgis`, `mysql2rgeo`, `trilogy`, and `litedb`). If you would like to use HairTrigger with another compatible variation, you can dynamically add it to `HairTrigger.hair_trigger_configuration`, for example:
344
+ ```ruby
345
+ HairTrigger.hair_trigger_configuration.postgresql_adapters << :postgresql_proxy
346
+ ```
347
+
317
348
  ## Gotchas
318
349
 
319
350
  * As is the case with `ActiveRecord::Base.update_all` or any direct SQL you do,
@@ -353,8 +384,8 @@ to manage all that w/ automagical gemfiles. So the tl;dr when testing locally is
353
384
 
354
385
  ## Compatibility
355
386
 
356
- * Ruby 2.3.0+
357
- * Rails 5.0+
387
+ * Ruby 3.0+
388
+ * Rails 6.1+
358
389
  * PostgreSQL 8.0+
359
390
  * MySQL 5.0.10+
360
391
  * SQLite 3.3.8+
@@ -363,4 +394,4 @@ to manage all that w/ automagical gemfiles. So the tl;dr when testing locally is
363
394
 
364
395
  ## Copyright
365
396
 
366
- Copyright (c) 2011-2022 Jon Jensen. See LICENSE.txt for further details.
397
+ Copyright (c) 2011-2024 Jon Jensen. See LICENSE.txt for further details.
@@ -27,11 +27,11 @@ module HairTrigger
27
27
  name_clause = options[:only] ? "IN ('" + options[:only].join("', '") + "')" : nil
28
28
  adapter_name = HairTrigger.adapter_name_for(self)
29
29
  case adapter_name
30
- when :sqlite
30
+ when *HairTrigger.hair_trigger_config.sqlite_adapters
31
31
  select_rows("SELECT name, sql FROM sqlite_master WHERE type = 'trigger' #{name_clause ? " AND name " + name_clause : ""}").each do |(name, definition)|
32
32
  triggers[name] = quote_table_name_in_trigger(definition) + ";\n"
33
33
  end
34
- when :mysql
34
+ when *HairTrigger.hair_trigger_config.mysql_adapters
35
35
  select_rows("SHOW TRIGGERS").each do |(name, event, table, actions, timing, created, sql_mode, definer)|
36
36
  definer = normalize_mysql_definer(definer)
37
37
  next if options[:only] && !options[:only].include?(name)
@@ -41,14 +41,14 @@ FOR EACH ROW
41
41
  #{actions}
42
42
  SQL
43
43
  end
44
- when :postgresql, :postgis
44
+ when *HairTrigger.hair_trigger_config.postgresql_adapters
45
45
  function_conditions = "(SELECT typname FROM pg_type WHERE oid = prorettype) = 'trigger'"
46
46
  function_conditions << <<-SQL unless options[:simple_check]
47
47
  AND oid IN (
48
48
  SELECT tgfoid
49
49
  FROM pg_trigger
50
50
  WHERE NOT tgisinternal AND tgconstrrelid = 0 AND tgrelid IN (
51
- SELECT oid FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
51
+ SELECT oid FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = '#{HairTrigger.pg_schema}')
52
52
  )
53
53
  )
54
54
  SQL
@@ -57,9 +57,9 @@ FOR EACH ROW
57
57
  SELECT tgname::varchar, pg_get_triggerdef(oid, true)
58
58
  FROM pg_trigger
59
59
  WHERE NOT tgisinternal AND tgconstrrelid = 0 AND tgrelid IN (
60
- SELECT oid FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
60
+ SELECT oid FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = '#{HairTrigger.pg_schema}')
61
61
  )
62
-
62
+
63
63
  #{name_clause ? " AND tgname::varchar " + name_clause : ""}
64
64
  UNION
65
65
  SELECT proname || '()', pg_get_functiondef(oid)
@@ -44,7 +44,7 @@ module HairTrigger
44
44
  end
45
45
 
46
46
  def name(name)
47
- @errors << ["trigger name cannot exceed 63 for postgres", :postgresql] if name.to_s.size > 63
47
+ @errors << ["trigger name cannot exceed 63 for postgres", *HairTrigger.hair_trigger_config.postgresql_adapters] if name.to_s.size > 63
48
48
  options[:name] = name.to_s
49
49
  end
50
50
 
@@ -54,7 +54,7 @@ module HairTrigger
54
54
  end
55
55
 
56
56
  def for_each(for_each)
57
- @errors << ["sqlite and mysql don't support FOR EACH STATEMENT triggers", :sqlite, :mysql] if for_each == :statement
57
+ @errors << ["sqlite and mysql don't support FOR EACH STATEMENT triggers", *HairTrigger.hair_trigger_config.sqlite_adapters, *HairTrigger.hair_trigger_config.mysql_adapters] if for_each == :statement
58
58
  raise DeclarationError, "invalid for_each" unless [:row, :statement].include?(for_each)
59
59
  options[:for_each] = for_each.to_s.upcase
60
60
  end
@@ -82,6 +82,18 @@ module HairTrigger
82
82
  options[:of] = columns
83
83
  end
84
84
 
85
+ def old_as(table)
86
+ raise DeclarationError, "`old_as' requested, but no table_name specified" unless table.present?
87
+ options[:referencing] ||= {}
88
+ options[:referencing][:old] = table
89
+ end
90
+
91
+ def new_as(table)
92
+ raise DeclarationError, "`new_as' requested, but no table_name specified" unless table.present?
93
+ options[:referencing] ||= {}
94
+ options[:referencing][:new] = table
95
+ end
96
+
85
97
  def declare(declarations)
86
98
  options[:declarations] = declarations
87
99
  end
@@ -95,9 +107,9 @@ module HairTrigger
95
107
  raise DeclarationError, "trigger security should be :invoker, :definer, CURRENT_USER, or a valid user (e.g. 'user'@'host')"
96
108
  end
97
109
  # sqlite default is n/a, mysql default is :definer, postgres default is :invoker
98
- @errors << ["sqlite doesn't support trigger security", :sqlite]
99
- @errors << ["postgresql doesn't support arbitrary users for trigger security", :postgresql] unless [:definer, :invoker].include?(user)
100
- @errors << ["mysql doesn't support invoker trigger security", :mysql] if user == :invoker
110
+ @errors << ["sqlite doesn't support trigger security", *HairTrigger.hair_trigger_config.sqlite_adapters]
111
+ @errors << ["postgresql doesn't support arbitrary users for trigger security", *HairTrigger.hair_trigger_config.postgresql_adapters] unless [:definer, :invoker].include?(user)
112
+ @errors << ["mysql doesn't support invoker trigger security", *HairTrigger.hair_trigger_config.mysql_adapters] if user == :invoker
101
113
  options[:security] = user
102
114
  end
103
115
 
@@ -110,8 +122,8 @@ module HairTrigger
110
122
  events << :insert if events.delete(:create)
111
123
  events << :delete if events.delete(:destroy)
112
124
  raise DeclarationError, "invalid events" unless events & [:insert, :update, :delete, :truncate] == events
113
- @errors << ["sqlite and mysql triggers may not be shared by multiple actions", :mysql, :sqlite] if events.size > 1
114
- @errors << ["sqlite and mysql do not support truncate triggers", :mysql, :sqlite] if events.include?(:truncate)
125
+ @errors << ["sqlite and mysql triggers may not be shared by multiple actions", *HairTrigger.hair_trigger_config.mysql_adapters, *HairTrigger.hair_trigger_config.sqlite_adapters] if events.size > 1
126
+ @errors << ["sqlite and mysql do not support truncate triggers", *HairTrigger.hair_trigger_config.mysql_adapters, *HairTrigger.hair_trigger_config.sqlite_adapters] if events.include?(:truncate)
115
127
  options[:events] = events.map{ |e| e.to_s.upcase }
116
128
  end
117
129
 
@@ -140,7 +152,7 @@ module HairTrigger
140
152
  def #{method}(*args, &block)
141
153
  @chained_calls << :#{method}
142
154
  if @triggers || @trigger_group
143
- @errors << ["mysql doesn't support #{method} within a trigger group", :mysql] unless [:name, :where, :all, :of].include?(:#{method})
155
+ @errors << ["mysql doesn't support #{method} within a trigger group", *HairTrigger.hair_trigger_config.mysql_adapters] unless [:name, :where, :all, :of].include?(:#{method})
144
156
  end
145
157
  set_#{method}(*args, &(block_given? ? block : nil))
146
158
  end
@@ -159,10 +171,10 @@ module HairTrigger
159
171
  METHOD
160
172
  end
161
173
  end
162
- chainable_methods :name, :on, :for_each, :before, :after, :where, :security, :timing, :events, :all, :nowrap, :of, :declare
174
+ chainable_methods :name, :on, :for_each, :before, :after, :where, :security, :timing, :events, :all, :nowrap, :of, :declare, :old_as, :new_as
163
175
 
164
176
  def create_grouped_trigger?
165
- adapter_name == :mysql
177
+ HairTrigger.hair_trigger_config.mysql_adapters.include?(adapter_name)
166
178
  end
167
179
 
168
180
  def prepare!
@@ -222,11 +234,11 @@ module HairTrigger
222
234
 
223
235
  [generate_drop_trigger] +
224
236
  [case adapter_name
225
- when :sqlite
237
+ when *HairTrigger.hair_trigger_config.sqlite_adapters
226
238
  generate_trigger_sqlite
227
- when :mysql
239
+ when *HairTrigger.hair_trigger_config.mysql_adapters
228
240
  generate_trigger_mysql
229
- when :postgresql, :postgis
241
+ when *HairTrigger.hair_trigger_config.postgresql_adapters
230
242
  generate_trigger_postgresql
231
243
  else
232
244
  raise GenerationError, "don't know how to build #{adapter_name} triggers yet"
@@ -306,6 +318,10 @@ module HairTrigger
306
318
  "where(#{prepared_where.inspect})"
307
319
  when :of
308
320
  "of(#{options[:of].inspect[1..-2]})"
321
+ when :old_as
322
+ "old_as(#{options[:referencing][:old].inspect})"
323
+ when :new_as
324
+ "new_as(#{options[:referencing][:new].inspect})"
309
325
  when :for_each
310
326
  "for_each(#{options[:for_each].downcase.to_sym.inspect})"
311
327
  when :declare
@@ -329,8 +345,8 @@ module HairTrigger
329
345
  def maybe_execute(&block)
330
346
  raise DeclarationError, "of may only be specified on update triggers" if options[:of] && options[:events] != ["UPDATE"]
331
347
  if block.arity > 0 # we're creating a trigger group, so set up some stuff and pass the buck
332
- @errors << ["trigger group must specify timing and event(s) for mysql", :mysql] unless options[:timing] && options[:events]
333
- @errors << ["nested trigger groups are not supported for mysql", :mysql] if @trigger_group
348
+ @errors << ["trigger group must specify timing and event(s) for mysql", *HairTrigger.hair_trigger_config.mysql_adapters] unless options[:timing] && options[:events]
349
+ @errors << ["nested trigger groups are not supported for mysql", *HairTrigger.hair_trigger_config.mysql_adapters] if @trigger_group
334
350
  @triggers = []
335
351
  block.call(self)
336
352
  raise DeclarationError, "trigger group did not define any triggers" if @triggers.empty?
@@ -359,9 +375,9 @@ module HairTrigger
359
375
  subtriggers = all_triggers(false)
360
376
  named_subtriggers = subtriggers.select{ |t| t.options[:name] }
361
377
  if named_subtriggers.present? && !options[:name]
362
- @warnings << ["nested triggers have explicit names, but trigger group does not. trigger name will be inferred", :mysql]
378
+ @warnings << ["nested triggers have explicit names, but trigger group does not. trigger name will be inferred", *HairTrigger.hair_trigger_config.mysql_adapters]
363
379
  elsif subtriggers.present? && !named_subtriggers.present? && options[:name]
364
- @warnings << ["trigger group has an explicit name, but nested triggers do not. trigger names will be inferred", :postgresql, :sqlite]
380
+ @warnings << ["trigger group has an explicit name, but nested triggers do not. trigger names will be inferred", *HairTrigger.hair_trigger_config.postgresql_adapters, *HairTrigger.hair_trigger_config.sqlite_adapters]
365
381
  end
366
382
  end
367
383
 
@@ -396,20 +412,37 @@ module HairTrigger
396
412
 
397
413
  def supports_of?
398
414
  case adapter_name
399
- when :sqlite
415
+ when *HairTrigger.hair_trigger_config.sqlite_adapters
400
416
  true
401
- when :postgresql, :postgis
417
+ when *HairTrigger.hair_trigger_config.postgresql_adapters
402
418
  db_version >= 90000
403
419
  else
404
420
  false
405
421
  end
406
422
  end
407
423
 
424
+ def referencing_clause(check_support = true)
425
+ if options[:referencing] && (!check_support || supports_referencing?)
426
+ "REFERENCING " + options[:referencing].map{ |k, v| "#{k.to_s.upcase} TABLE AS #{v}" }.join(" ")
427
+ end
428
+ end
429
+
430
+ def supports_referencing?
431
+ case adapter_name
432
+ when *HairTrigger.hair_trigger_config.sqlite_adapters, *HairTrigger.hair_trigger_config.mysql_adapters
433
+ false
434
+ when *HairTrigger.hair_trigger_config.postgresql_adapters
435
+ db_version >= 100000
436
+ else
437
+ false
438
+ end
439
+ end
440
+
408
441
  def generate_drop_trigger
409
442
  case adapter_name
410
- when :sqlite, :mysql
443
+ when *HairTrigger.hair_trigger_config.sqlite_adapters, *HairTrigger.hair_trigger_config.mysql_adapters
411
444
  "DROP TRIGGER IF EXISTS #{prepared_name};\n"
412
- when :postgresql, :postgis
445
+ when *HairTrigger.hair_trigger_config.postgresql_adapters
413
446
  "DROP TRIGGER IF EXISTS #{prepared_name} ON #{adapter.quote_table_name(options[:table])};\nDROP FUNCTION IF EXISTS #{adapter.quote_table_name(prepared_name)}();\n"
414
447
  else
415
448
  raise GenerationError, "don't know how to drop #{adapter_name} triggers yet"
@@ -433,6 +466,7 @@ END;
433
466
  raise GenerationError, "security cannot be used in conjunction with nowrap" if options[:nowrap] && options[:security]
434
467
  raise GenerationError, "where can only be used in conjunction with nowrap on postgres 9.0 and greater" if options[:nowrap] && prepared_where && db_version < 90000
435
468
  raise GenerationError, "of can only be used in conjunction with nowrap on postgres 9.1 and greater" if options[:nowrap] && options[:of] && db_version < 90100
469
+ raise GenerationError, "referencing can only be used on postgres 10.0 and greater" if options[:referencing] && db_version < 100000
436
470
 
437
471
  sql = ''
438
472
 
@@ -472,6 +506,7 @@ $$ LANGUAGE plpgsql#{security ? " SECURITY #{security.to_s.upcase}" : ""};
472
506
 
473
507
  [sql, <<-SQL]
474
508
  CREATE TRIGGER #{prepared_name} #{options[:timing]} #{options[:events].join(" OR ")} #{of_clause}ON #{adapter.quote_table_name(options[:table])}
509
+ #{referencing_clause}
475
510
  FOR EACH #{options[:for_each]}#{prepared_where && db_version >= 90000 ? " WHEN (" + prepared_where + ')': ''} EXECUTE PROCEDURE #{trigger_action};
476
511
  SQL
477
512
  end
@@ -497,7 +532,7 @@ BEGIN
497
532
 
498
533
  def db_version
499
534
  @db_version ||= case adapter_name
500
- when :postgresql, :postgis
535
+ when *HairTrigger.hair_trigger_config.postgresql_adapters
501
536
  adapter.send(:postgresql_version)
502
537
  end
503
538
  end
@@ -0,0 +1,11 @@
1
+ module HairTrigger
2
+ class Configuration
3
+ attr_accessor :postgresql_adapters, :mysql_adapters, :sqlite_adapters
4
+
5
+ def initialize(postgresql_adapters: [], mysql_adapters: [], sqlite_adapters: [])
6
+ @postgresql_adapters = postgresql_adapters
7
+ @mysql_adapters = mysql_adapters
8
+ @sqlite_adapters = sqlite_adapters
9
+ end
10
+ end
11
+ end
@@ -74,7 +74,7 @@ module HairTrigger
74
74
  def normalize_trigger(name, definition, type)
75
75
  @adapter_name = @connection.adapter_name.downcase.to_sym
76
76
 
77
- return definition unless @adapter_name == :postgresql || @adapter_name == :postgis
77
+ return definition unless HairTrigger.hair_trigger_config.postgresql_adapters.include?(@adapter_name)
78
78
  # because postgres does not preserve the original CREATE TRIGGER/
79
79
  # FUNCTION statements, its decompiled reconstruction will not match
80
80
  # ours. we work around it by creating our generated trigger/function,
@@ -1,5 +1,5 @@
1
1
  module HairTrigger
2
- VERSION = "1.0.0"
2
+ VERSION = "1.3.1"
3
3
 
4
4
  def VERSION.<=>(other)
5
5
  split(/\./).map(&:to_i) <=> other.split(/\./).map(&:to_i)
data/lib/hair_trigger.rb CHANGED
@@ -8,12 +8,28 @@ require 'hair_trigger/schema_dumper'
8
8
  require 'hair_trigger/railtie' if defined?(Rails::Railtie)
9
9
 
10
10
  module HairTrigger
11
+ POSTGRESQL_ADAPTERS = %i[postgresql postgis]
12
+ MYSQL_ADAPTERS = %i[mysql mysql2rgeo trilogy]
13
+ SQLITE_ADAPTERS = %i[sqlite litedb]
11
14
 
15
+ autoload :Configuration, 'hair_trigger/configuration'
12
16
  autoload :Builder, 'hair_trigger/builder'
13
17
  autoload :MigrationReader, 'hair_trigger/migration_reader'
14
18
 
15
19
  class << self
16
- attr_writer :model_path, :schema_rb_path, :migration_path
20
+ attr_writer :model_path, :schema_rb_path, :migration_path, :pg_schema
21
+
22
+ def configure
23
+ yield hair_trigger_config
24
+ end
25
+
26
+ def hair_trigger_config
27
+ @hair_trigger_config ||= Configuration.new(
28
+ postgresql_adapters: POSTGRESQL_ADAPTERS,
29
+ mysql_adapters: MYSQL_ADAPTERS,
30
+ sqlite_adapters: SQLITE_ADAPTERS
31
+ )
32
+ end
17
33
 
18
34
  def current_triggers
19
35
  # see what the models say there should be
@@ -39,7 +55,12 @@ module HairTrigger
39
55
  end
40
56
 
41
57
  def migrator
42
- if ActiveRecord::VERSION::STRING >= "7.1."
58
+ if Gem::Version.new("7.2.0") <= ActiveRecord.gem_version
59
+ connection = ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool
60
+ schema_migration = connection.schema_migration
61
+ migrations = ActiveRecord::MigrationContext.new(migration_path, schema_migration).migrations
62
+ ActiveRecord::Migrator.new(:up, migrations, schema_migration, ActiveRecord::InternalMetadata.new(connection))
63
+ elsif Gem::Version.new("7.1.0") <= ActiveRecord.gem_version
43
64
  connection = ActiveRecord::Tasks::DatabaseTasks.migration_connection
44
65
  schema_migration = connection.schema_migration
45
66
  migrations = ActiveRecord::MigrationContext.new(migration_path, schema_migration).migrations
@@ -223,6 +244,10 @@ end
223
244
  @migration_path ||= 'db/migrate'
224
245
  end
225
246
 
247
+ def pg_schema
248
+ @pg_schema ||= 'public'
249
+ end
250
+
226
251
  def adapter_name_for(adapter)
227
252
  adapter.adapter_name.downcase.sub(/\d$/, '').to_sym
228
253
  end
@@ -11,13 +11,56 @@ namespace :db do
11
11
  namespace :schema do
12
12
  desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
13
13
  task :dump => :environment do
14
+ format = ActiveRecord.respond_to?(:schema_format) ? ActiveRecord.schema_format : ActiveRecord::Base.schema_format
15
+ next unless format == :ruby
16
+
14
17
  require 'active_record/schema_dumper'
15
- filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
16
- ActiveRecord::SchemaDumper.previous_schema = File.exist?(filename) ? File.read(filename) : nil
17
- File.open(filename, "w") do |file|
18
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
18
+
19
+ databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
20
+
21
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
22
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)
23
+ connection = get_connection(ActiveRecord::Base.establish_connection(db_config))
24
+
25
+ filename = dump_filename(db_config.name)
26
+ ActiveRecord::SchemaDumper.previous_schema = File.exist?(filename) ? File.read(filename) : nil
27
+
28
+ File.open(filename, "w") do |file|
29
+
30
+ ActiveRecord::SchemaDumper.dump(connection, file)
31
+ end
19
32
  end
33
+
20
34
  Rake::Task["db:schema:dump"].reenable
21
35
  end
36
+
37
+ def get_connection(connection_pool)
38
+ if Gem::Version.new("7.2.0") <= ActiveRecord.gem_version
39
+ connection_pool
40
+ else
41
+ connection_pool.connection
42
+ end
43
+ end
44
+
45
+ def schema_file_type(format)
46
+ case format
47
+ when :ruby
48
+ "schema.rb"
49
+ when :sql
50
+ "structure.sql"
51
+ end
52
+ end
53
+
54
+ # code adopted from activerecord/lib/active_record/tasks/database_tasks.rb#L441
55
+ def dump_filename(db_config_name)
56
+ format = ActiveRecord.respond_to?(:schema_format) ? ActiveRecord.schema_format : ActiveRecord::Base.schema_format
57
+ filename = if ActiveRecord::Base.configurations.primary?(db_config_name)
58
+ schema_file_type(format)
59
+ else
60
+ "#{db_config_name}_#{schema_file_type(format)}"
61
+ end
62
+
63
+ ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
64
+ end
22
65
  end
23
66
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hairtrigger
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Jensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-13 00:00:00.000000000 Z
11
+ date: 2025-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '6.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '8'
22
+ version: '9'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '6.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '8'
32
+ version: '9'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: ruby_parser
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +72,7 @@ files:
72
72
  - lib/hair_trigger/adapter.rb
73
73
  - lib/hair_trigger/base.rb
74
74
  - lib/hair_trigger/builder.rb
75
+ - lib/hair_trigger/configuration.rb
75
76
  - lib/hair_trigger/migration_reader.rb
76
77
  - lib/hair_trigger/migrator.rb
77
78
  - lib/hair_trigger/railtie.rb
@@ -91,14 +92,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
91
92
  requirements:
92
93
  - - ">="
93
94
  - !ruby/object:Gem::Version
94
- version: 2.5.0
95
+ version: '3.0'
95
96
  required_rubygems_version: !ruby/object:Gem::Requirement
96
97
  requirements:
97
98
  - - ">="
98
99
  - !ruby/object:Gem::Version
99
100
  version: '0'
100
101
  requirements: []
101
- rubygems_version: 3.3.3
102
+ rubygems_version: 3.5.3
102
103
  signing_key:
103
104
  specification_version: 4
104
105
  summary: easy database triggers for active record