Skip to content

Commit c708810

Browse files
authored
Add configuration class for custom adapters (#135)
The list of supported adapters is hardcoded, which makes it difficult to use this gem on custom adapters. We use `postgresql_proxy` from `active_record_proxy_adapters`, for instance. Currently, we need to monkey patch the gem and hot swap the constant `HairTrigger::POSTGRESQL_ADAPTERS` when the application boots to make it work with the proxy adapter. Although it works, it's not ideal. I've added a new configuration class whose values will default to the current ones (`%i[mysql2 trilogy]` for `mysql_adapters`, `%i[postgresql postgis]` for `postgresql_adapters` , and `%i[sqlite litedb]` for `sqlite_adapters`), but allow the user to add custom adapters for each. I don't expect any breaking changes here as the values fall back to the existing constants, but please double check. Thanks!
1 parent 0cdd699 commit c708810

File tree

5 files changed

+51
-27
lines changed

5 files changed

+51
-27
lines changed

lib/hair_trigger.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,25 @@ module HairTrigger
1212
MYSQL_ADAPTERS = %i[mysql mysql2rgeo trilogy]
1313
SQLITE_ADAPTERS = %i[sqlite litedb]
1414

15+
autoload :Configuration, 'hair_trigger/configuration'
1516
autoload :Builder, 'hair_trigger/builder'
1617
autoload :MigrationReader, 'hair_trigger/migration_reader'
1718

1819
class << self
1920
attr_writer :model_path, :schema_rb_path, :migration_path, :pg_schema
2021

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
33+
2134
def current_triggers
2235
# see what the models say there should be
2336
canonical_triggers = models.map(&:triggers).flatten.compact

lib/hair_trigger/adapter.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ def triggers(options = {})
2727
name_clause = options[:only] ? "IN ('" + options[:only].join("', '") + "')" : nil
2828
adapter_name = HairTrigger.adapter_name_for(self)
2929
case adapter_name
30-
when *HairTrigger::SQLITE_ADAPTERS
30+
when *HairTrigger.hair_trigger_config.sqlite_adapters
3131
select_rows("SELECT name, sql FROM sqlite_master WHERE type = 'trigger' #{name_clause ? " AND name " + name_clause : ""}").each do |(name, definition)|
3232
triggers[name] = quote_table_name_in_trigger(definition) + ";\n"
3333
end
34-
when *HairTrigger::MYSQL_ADAPTERS
34+
when *HairTrigger.hair_trigger_config.mysql_adapters
3535
select_rows("SHOW TRIGGERS").each do |(name, event, table, actions, timing, created, sql_mode, definer)|
3636
definer = normalize_mysql_definer(definer)
3737
next if options[:only] && !options[:only].include?(name)
@@ -41,7 +41,7 @@ def triggers(options = {})
4141
#{actions}
4242
SQL
4343
end
44-
when *POSTGRESQL_ADAPTERS
44+
when *HairTrigger.hair_trigger_config.postgresql_adapters
4545
function_conditions = "(SELECT typname FROM pg_type WHERE oid = prorettype) = 'trigger'"
4646
function_conditions << <<-SQL unless options[:simple_check]
4747
AND oid IN (

lib/hair_trigger/builder.rb

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def drop_triggers
4444
end
4545

4646
def name(name)
47-
@errors << ["trigger name cannot exceed 63 for postgres", *HairTrigger::POSTGRESQL_ADAPTERS] 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
4848
options[:name] = name.to_s
4949
end
5050

@@ -54,7 +54,7 @@ def on(table)
5454
end
5555

5656
def for_each(for_each)
57-
@errors << ["sqlite and mysql don't support FOR EACH STATEMENT triggers", *HairTrigger::SQLITE_ADAPTERS, *HairTrigger::MYSQL_ADAPTERS] 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
5858
raise DeclarationError, "invalid for_each" unless [:row, :statement].include?(for_each)
5959
options[:for_each] = for_each.to_s.upcase
6060
end
@@ -107,9 +107,9 @@ def security(user)
107107
raise DeclarationError, "trigger security should be :invoker, :definer, CURRENT_USER, or a valid user (e.g. 'user'@'host')"
108108
end
109109
# sqlite default is n/a, mysql default is :definer, postgres default is :invoker
110-
@errors << ["sqlite doesn't support trigger security", *HairTrigger::SQLITE_ADAPTERS]
111-
@errors << ["postgresql doesn't support arbitrary users for trigger security", *HairTrigger::POSTGRESQL_ADAPTERS] unless [:definer, :invoker].include?(user)
112-
@errors << ["mysql doesn't support invoker trigger security", *HairTrigger::MYSQL_ADAPTERS] 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
113113
options[:security] = user
114114
end
115115

@@ -122,8 +122,8 @@ def events(*events)
122122
events << :insert if events.delete(:create)
123123
events << :delete if events.delete(:destroy)
124124
raise DeclarationError, "invalid events" unless events & [:insert, :update, :delete, :truncate] == events
125-
@errors << ["sqlite and mysql triggers may not be shared by multiple actions", *HairTrigger::MYSQL_ADAPTERS, *HairTrigger::SQLITE_ADAPTERS] if events.size > 1
126-
@errors << ["sqlite and mysql do not support truncate triggers", *HairTrigger::MYSQL_ADAPTERS, *HairTrigger::SQLITE_ADAPTERS] 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)
127127
options[:events] = events.map{ |e| e.to_s.upcase }
128128
end
129129

@@ -152,7 +152,7 @@ def self.chainable_methods(*methods)
152152
def #{method}(*args, &block)
153153
@chained_calls << :#{method}
154154
if @triggers || @trigger_group
155-
@errors << ["mysql doesn't support #{method} within a trigger group", *HairTrigger::MYSQL_ADAPTERS] 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})
156156
end
157157
set_#{method}(*args, &(block_given? ? block : nil))
158158
end
@@ -174,7 +174,7 @@ def set_#{method}(*args, &block)
174174
chainable_methods :name, :on, :for_each, :before, :after, :where, :security, :timing, :events, :all, :nowrap, :of, :declare, :old_as, :new_as
175175

176176
def create_grouped_trigger?
177-
HairTrigger::MYSQL_ADAPTERS.include?(adapter_name)
177+
HairTrigger.hair_trigger_config.mysql_adapters.include?(adapter_name)
178178
end
179179

180180
def prepare!
@@ -234,11 +234,11 @@ def generate(validate = true)
234234

235235
[generate_drop_trigger] +
236236
[case adapter_name
237-
when *HairTrigger::SQLITE_ADAPTERS
237+
when *HairTrigger.hair_trigger_config.sqlite_adapters
238238
generate_trigger_sqlite
239-
when *HairTrigger::MYSQL_ADAPTERS
239+
when *HairTrigger.hair_trigger_config.mysql_adapters
240240
generate_trigger_mysql
241-
when *HairTrigger::POSTGRESQL_ADAPTERS
241+
when *HairTrigger.hair_trigger_config.postgresql_adapters
242242
generate_trigger_postgresql
243243
else
244244
raise GenerationError, "don't know how to build #{adapter_name} triggers yet"
@@ -345,8 +345,8 @@ def actions_to_ruby(indent = '')
345345
def maybe_execute(&block)
346346
raise DeclarationError, "of may only be specified on update triggers" if options[:of] && options[:events] != ["UPDATE"]
347347
if block.arity > 0 # we're creating a trigger group, so set up some stuff and pass the buck
348-
@errors << ["trigger group must specify timing and event(s) for mysql", *HairTrigger::MYSQL_ADAPTERS] unless options[:timing] && options[:events]
349-
@errors << ["nested trigger groups are not supported for mysql", *HairTrigger::MYSQL_ADAPTERS] 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
350350
@triggers = []
351351
block.call(self)
352352
raise DeclarationError, "trigger group did not define any triggers" if @triggers.empty?
@@ -375,9 +375,9 @@ def validate_names!
375375
subtriggers = all_triggers(false)
376376
named_subtriggers = subtriggers.select{ |t| t.options[:name] }
377377
if named_subtriggers.present? && !options[:name]
378-
@warnings << ["nested triggers have explicit names, but trigger group does not. trigger name will be inferred", *HairTrigger::MYSQL_ADAPTERS]
378+
@warnings << ["nested triggers have explicit names, but trigger group does not. trigger name will be inferred", *HairTrigger.hair_trigger_config.mysql_adapters]
379379
elsif subtriggers.present? && !named_subtriggers.present? && options[:name]
380-
@warnings << ["trigger group has an explicit name, but nested triggers do not. trigger names will be inferred", *HairTrigger::POSTGRESQL_ADAPTERS, *HairTrigger::SQLITE_ADAPTERS]
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]
381381
end
382382
end
383383

@@ -412,9 +412,9 @@ def declarations
412412

413413
def supports_of?
414414
case adapter_name
415-
when *HairTrigger::SQLITE_ADAPTERS
415+
when *HairTrigger.hair_trigger_config.sqlite_adapters
416416
true
417-
when *HairTrigger::POSTGRESQL_ADAPTERS
417+
when *HairTrigger.hair_trigger_config.postgresql_adapters
418418
db_version >= 90000
419419
else
420420
false
@@ -429,9 +429,9 @@ def referencing_clause(check_support = true)
429429

430430
def supports_referencing?
431431
case adapter_name
432-
when *HairTrigger::SQLITE_ADAPTERS, *HairTrigger::MYSQL_ADAPTERS
432+
when *HairTrigger.hair_trigger_config.sqlite_adapters, *HairTrigger.hair_trigger_config.mysql_adapters
433433
false
434-
when *HairTrigger::POSTGRESQL_ADAPTERS
434+
when *HairTrigger.hair_trigger_config.postgresql_adapters
435435
db_version >= 100000
436436
else
437437
false
@@ -440,9 +440,9 @@ def supports_referencing?
440440

441441
def generate_drop_trigger
442442
case adapter_name
443-
when *HairTrigger::SQLITE_ADAPTERS, *HairTrigger::MYSQL_ADAPTERS
443+
when *HairTrigger.hair_trigger_config.sqlite_adapters, *HairTrigger.hair_trigger_config.mysql_adapters
444444
"DROP TRIGGER IF EXISTS #{prepared_name};\n"
445-
when *HairTrigger::POSTGRESQL_ADAPTERS
445+
when *HairTrigger.hair_trigger_config.postgresql_adapters
446446
"DROP TRIGGER IF EXISTS #{prepared_name} ON #{adapter.quote_table_name(options[:table])};\nDROP FUNCTION IF EXISTS #{adapter.quote_table_name(prepared_name)}();\n"
447447
else
448448
raise GenerationError, "don't know how to drop #{adapter_name} triggers yet"
@@ -532,7 +532,7 @@ def generate_trigger_mysql
532532

533533
def db_version
534534
@db_version ||= case adapter_name
535-
when *HairTrigger::POSTGRESQL_ADAPTERS
535+
when *HairTrigger.hair_trigger_config.postgresql_adapters
536536
adapter.send(:postgresql_version)
537537
end
538538
end

lib/hair_trigger/configuration.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module HairTrigger
2+
class Configuration
3+
attr_accessor :postgresql_adapters, :mysql_adapters, :sql_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

lib/hair_trigger/schema_dumper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def triggers(stream)
7474
def normalize_trigger(name, definition, type)
7575
@adapter_name = @connection.adapter_name.downcase.to_sym
7676

77-
return definition unless HairTrigger::POSTGRESQL_ADAPTERS.include?(@adapter_name)
77+
return definition unless HairTrigger.hair_trigger_config.postgresql_adapters.include?(@adapter_name)
7878
# because postgres does not preserve the original CREATE TRIGGER/
7979
# FUNCTION statements, its decompiled reconstruction will not match
8080
# ours. we work around it by creating our generated trigger/function,

0 commit comments

Comments
 (0)