schema_plus_foreign_keys 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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +21 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +200 -0
  7. data/Rakefile +9 -0
  8. data/gemfiles/Gemfile.base +4 -0
  9. data/gemfiles/activerecord-4.2.0/Gemfile.base +3 -0
  10. data/gemfiles/activerecord-4.2.0/Gemfile.mysql2 +10 -0
  11. data/gemfiles/activerecord-4.2.0/Gemfile.postgresql +10 -0
  12. data/gemfiles/activerecord-4.2.0/Gemfile.sqlite3 +10 -0
  13. data/gemfiles/activerecord-4.2.1/Gemfile.base +3 -0
  14. data/gemfiles/activerecord-4.2.1/Gemfile.mysql2 +10 -0
  15. data/gemfiles/activerecord-4.2.1/Gemfile.postgresql +10 -0
  16. data/gemfiles/activerecord-4.2.1/Gemfile.sqlite3 +10 -0
  17. data/lib/schema_plus/foreign_keys.rb +78 -0
  18. data/lib/schema_plus/foreign_keys/active_record/base.rb +33 -0
  19. data/lib/schema_plus/foreign_keys/active_record/connection_adapters/abstract_adapter.rb +168 -0
  20. data/lib/schema_plus/foreign_keys/active_record/connection_adapters/foreign_key_definition.rb +137 -0
  21. data/lib/schema_plus/foreign_keys/active_record/connection_adapters/mysql2_adapter.rb +126 -0
  22. data/lib/schema_plus/foreign_keys/active_record/connection_adapters/postgresql_adapter.rb +89 -0
  23. data/lib/schema_plus/foreign_keys/active_record/connection_adapters/sqlite3_adapter.rb +77 -0
  24. data/lib/schema_plus/foreign_keys/active_record/connection_adapters/table_definition.rb +108 -0
  25. data/lib/schema_plus/foreign_keys/active_record/migration/command_recorder.rb +29 -0
  26. data/lib/schema_plus/foreign_keys/middleware/dumper.rb +88 -0
  27. data/lib/schema_plus/foreign_keys/middleware/migration.rb +147 -0
  28. data/lib/schema_plus/foreign_keys/middleware/model.rb +15 -0
  29. data/lib/schema_plus/foreign_keys/middleware/mysql.rb +20 -0
  30. data/lib/schema_plus/foreign_keys/middleware/sql.rb +27 -0
  31. data/lib/schema_plus/foreign_keys/version.rb +5 -0
  32. data/lib/schema_plus_foreign_keys.rb +1 -0
  33. data/schema_dev.yml +9 -0
  34. data/schema_plus_foreign_keys.gemspec +31 -0
  35. data/spec/deprecation_spec.rb +161 -0
  36. data/spec/foreign_key_definition_spec.rb +34 -0
  37. data/spec/foreign_key_spec.rb +207 -0
  38. data/spec/migration_spec.rb +570 -0
  39. data/spec/named_schemas_spec.rb +136 -0
  40. data/spec/schema_dumper_spec.rb +257 -0
  41. data/spec/spec_helper.rb +60 -0
  42. data/spec/support/reference.rb +79 -0
  43. metadata +221 -0
@@ -0,0 +1,89 @@
1
+ module SchemaPlus::ForeignKeys
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+
5
+ # The Postgresql adapter implements the SchemaPlus::ForeignKeys extensions and
6
+ # enhancements
7
+ module PostgresqlAdapter
8
+
9
+ def rename_table(oldname, newname) #:nodoc:
10
+ super
11
+ rename_foreign_keys(oldname, newname)
12
+ end
13
+
14
+ def foreign_keys(table_name, name = nil) #:nodoc:
15
+ load_foreign_keys(<<-SQL, name)
16
+ SELECT f.conname, pg_get_constraintdef(f.oid), t.relname
17
+ FROM pg_class t, pg_constraint f
18
+ WHERE f.conrelid = t.oid
19
+ AND f.contype = 'f'
20
+ AND t.relname = '#{table_name_without_namespace(table_name)}'
21
+ AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{namespace_sql(table_name)} )
22
+ SQL
23
+ end
24
+
25
+ def reverse_foreign_keys(table_name, name = nil) #:nodoc:
26
+ load_foreign_keys(<<-SQL, name)
27
+ SELECT f.conname, pg_get_constraintdef(f.oid), t2.relname
28
+ FROM pg_class t, pg_class t2, pg_constraint f
29
+ WHERE f.confrelid = t.oid
30
+ AND f.conrelid = t2.oid
31
+ AND f.contype = 'f'
32
+ AND t.relname = '#{table_name_without_namespace(table_name)}'
33
+ AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{namespace_sql(table_name)} )
34
+ SQL
35
+ end
36
+
37
+ private
38
+
39
+ def unquote(name)
40
+ return name.map { |name| unquote(name) } if name.is_a?(Array)
41
+ name.sub(/^["`](.*)["`]$/, '\1')
42
+ end
43
+
44
+ def namespace_sql(table_name)
45
+ (table_name.to_s =~ /(.*)[.]/) ? "'#{$1}'" : "ANY (current_schemas(false))"
46
+ end
47
+
48
+ def table_name_without_namespace(table_name)
49
+ table_name.to_s.sub /.*[.]/, ''
50
+ end
51
+
52
+ def load_foreign_keys(sql, name = nil) #:nodoc:
53
+ foreign_keys = []
54
+
55
+ query(sql, name).each do |row|
56
+ if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE)( (INITIALLY DEFERRED|INITIALLY IMMEDIATE))?)?$/
57
+ name = row[0]
58
+ from_table = unquote(row[2])
59
+ columns = unquote($1.split(', '))
60
+ to_table = unquote($2)
61
+ primary_keys = unquote($3.split(', '))
62
+ on_update = $5
63
+ on_delete = $7
64
+ deferrable = $9 == "DEFERRABLE"
65
+ deferrable = :initially_deferred if ($11 == "INITIALLY DEFERRED" )
66
+ on_update = ForeignKeyDefinition::ACTION_LOOKUP[on_update] || :no_action
67
+ on_delete = ForeignKeyDefinition::ACTION_LOOKUP[on_delete] || :no_action
68
+
69
+ options = { :name => name,
70
+ :on_delete => on_delete,
71
+ :on_update => on_update,
72
+ :column => columns,
73
+ :primary_key => primary_keys,
74
+ :deferrable => deferrable }
75
+
76
+ foreign_keys << ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
77
+ from_table,
78
+ to_table.sub(/^"(.*)"$/, '\1'),
79
+ options)
80
+ end
81
+ end
82
+
83
+ foreign_keys
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,77 @@
1
+ module SchemaPlus::ForeignKeys
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+
5
+ # SchemaPlus::ForeignKeys includes an Sqlite3 implementation of the AbstractAdapter
6
+ # extensions.
7
+ module Sqlite3Adapter
8
+
9
+ # :enddoc:
10
+
11
+ def add_foreign_key(table_name, to_table, options = {})
12
+ raise NotImplementedError, "Sqlite3 does not support altering a table to add foreign key constraints (table #{table_name.inspect} to #{to_table.inspect})"
13
+ end
14
+
15
+ def remove_foreign_key(table_name, *args)
16
+ raise NotImplementedError, "Sqlite3 does not support altering a table to remove foreign key constraints (table #{table_name.inspect} constraint #{args.inspect})"
17
+ end
18
+
19
+ def foreign_keys(table_name, name = nil)
20
+ get_foreign_keys(table_name, name)
21
+ end
22
+
23
+ def reverse_foreign_keys(table_name, name = nil)
24
+ get_foreign_keys(nil, name).select{|definition| definition.to_table == table_name}
25
+ end
26
+
27
+ protected
28
+
29
+ def get_foreign_keys(table_name = nil, name = nil)
30
+ results = execute(<<-SQL, name)
31
+ SELECT name, sql FROM sqlite_master
32
+ WHERE type='table' #{table_name && %" AND name='#{table_name}' "}
33
+ SQL
34
+
35
+ re = %r[
36
+ \b(CONSTRAINT\s+(\S+)\s+)?
37
+ FOREIGN\s+KEY\s* \(\s*[`"](.+?)[`"]\s*\)
38
+ \s*REFERENCES\s*[`"](.+?)[`"]\s*\((.+?)\)
39
+ (\s+ON\s+UPDATE\s+(.+?))?
40
+ (\s*ON\s+DELETE\s+(.+?))?
41
+ (\s*DEFERRABLE(\s+INITIALLY\s+DEFERRED)?)?
42
+ \s*[,)]
43
+ ]x
44
+
45
+ foreign_keys = []
46
+ results.each do |row|
47
+ from_table = row["name"]
48
+ row["sql"].scan(re).each do |d0, name, columns, to_table, primary_keys, d1, on_update, d2, on_delete, deferrable, initially_deferred|
49
+ columns = columns.gsub(/`/, '').split(', ')
50
+
51
+ primary_keys = primary_keys.gsub(/[`"]/, '').split(', ')
52
+ on_update = ForeignKeyDefinition::ACTION_LOOKUP[on_update] || :no_action
53
+ on_delete = ForeignKeyDefinition::ACTION_LOOKUP[on_delete] || :no_action
54
+ deferrable = deferrable ? (initially_deferred ? :initially_deferred : true) : false
55
+
56
+ options = { :name => name,
57
+ :on_update => on_update,
58
+ :on_delete => on_delete,
59
+ :column => columns,
60
+ :primary_key => primary_keys,
61
+ :deferrable => deferrable }
62
+
63
+ foreign_keys << ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
64
+ from_table,
65
+ to_table,
66
+ options)
67
+ end
68
+ end
69
+
70
+ foreign_keys
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,108 @@
1
+ module SchemaPlus::ForeignKeys::ActiveRecord::ConnectionAdapters
2
+
3
+ #
4
+ # SchemaPlus::ForeignKeys adds several methods to TableDefinition, allowing indexes
5
+ # and foreign key constraints to be defined within a
6
+ # <tt>create_table</tt> block of a migration, allowing for better
7
+ # encapsulation and more DRY definitions.
8
+ #
9
+ # For example, without SchemaPlus::ForeignKeys you might define a table like this:
10
+ #
11
+ # create_table :widgets do |t|
12
+ # t.string :name
13
+ # end
14
+ # add_index :widgets, :name
15
+ #
16
+ # But with SchemaPlus::ForeignKeys, the index can be defined within the create_table
17
+ # block, so you don't need to repeat the table name:
18
+ #
19
+ # create_table :widgets do |t|
20
+ # t.string :name
21
+ # t.index :name
22
+ # end
23
+ #
24
+ # Even more DRY, you can define the index as part of the column
25
+ # definition, via:
26
+ #
27
+ # create_table :widgets do |t|
28
+ # t.string :name, :index => true
29
+ # end
30
+ #
31
+ # For details about the :index option (including unique and multi-column indexes), see the
32
+ # documentation for Migration::ClassMethods#add_column
33
+ #
34
+ # SchemaPlus::ForeignKeys also supports creation of foreign key constraints analogously, using Migration::ClassMethods#add_foreign_key or TableDefinition#foreign_key or as part of the column definition, for example:
35
+ #
36
+ # create_table :posts do |t| # not DRY
37
+ # t.integer :author_id
38
+ # end
39
+ # add_foreign_key :posts, :author_id, :references => :authors
40
+ #
41
+ # create_table :posts do |t| # DRYer
42
+ # t.integer :author_id
43
+ # t.foreign_key :author_id, :references => :authors
44
+ # end
45
+ #
46
+ # create_table :posts do |t| # Dryest
47
+ # t.integer :author_id, :foreign_key => true
48
+ # end
49
+ #
50
+ # <b>NOTE:</b> In the standard configuration, SchemaPlus::ForeignKeys automatically
51
+ # creates foreign key constraints for columns whose names end in
52
+ # <tt>_id</tt>. So the above examples are redundant, unless automatic
53
+ # creation was disabled at initialization in the global Config.
54
+ #
55
+ # SchemaPlus::ForeignKeys likewise by default automatically creates foreign key constraints for
56
+ # columns defined via <tt>t.references</tt>. However, SchemaPlus::ForeignKeys does not create
57
+ # foreign key constraints if the <tt>:polymorphic</tt> option is true
58
+ #
59
+ # Finally, the configuration for foreign keys can be overriden on a per-table
60
+ # basis by passing Config options to Migration::ClassMethods#create_table, such as
61
+ #
62
+ # create_table :students, :foreign_keys => {:auto_create => false} do
63
+ # t.integer :student_id
64
+ # end
65
+ #
66
+ module TableDefinition
67
+
68
+ attr_accessor :schema_plus_foreign_keys_config #:nodoc:
69
+
70
+ if ActiveRecord.version == Gem::Version.new("4.2.0")
71
+ def foreign_keys
72
+ @foreign_keys ||= []
73
+ end
74
+
75
+ def foreign_keys_for_table(*)
76
+ foreign_keys
77
+ end
78
+ else
79
+ def foreign_keys_for_table(table)
80
+ foreign_keys[table] ||= []
81
+ end
82
+ end
83
+
84
+ def foreign_key(*args) # (column_names, to_table, primary_key=nil, options=nil)
85
+ options = args.extract_options!
86
+ case args.length
87
+ when 1
88
+ to_table = args[0]
89
+ column_names = "#{to_table.to_s.singularize}_id"
90
+ when 2
91
+ column_names, to_table = args
92
+ when 3
93
+ ActiveSupport::Deprecation.warn "positional arg for foreign primary key is deprecated, use :primary_key option instead"
94
+ column_names, to_table, primary_key = args
95
+ options.merge!(:primary_key => primary_key)
96
+ else
97
+ raise ArgumentError, "wrong number of arguments (#{args.length}) for foreign_key(column_names, table_name, options)"
98
+ end
99
+
100
+ options.merge!(:column => column_names)
101
+ options.reverse_merge!(:name => ForeignKeyDefinition.default_name(self.name, column_names))
102
+ fk = ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(self.name, AbstractAdapter.proper_table_name(to_table), options)
103
+ foreign_keys_for_table(fk.to_table) << fk
104
+ self
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,29 @@
1
+ module SchemaPlus::ForeignKeys
2
+ module ActiveRecord
3
+ module Migration
4
+ module CommandRecorder
5
+
6
+ attr_accessor :schema_plus_foreign_keys_config #:nodoc:
7
+
8
+ # seems like this is fixing a rails bug:
9
+ # change_table foo, :bulk => true { |t| t.references :bar }
10
+ # results in an 'unknown method :add_reference_sql' (with mysql2)
11
+ #
12
+ # should track it down separately and submit a patch/fix to rails
13
+ #
14
+ def add_reference(table_name, ref_name, options = {}) #:nodoc:
15
+ polymorphic = options.delete(:polymorphic)
16
+ options[:references] = nil if polymorphic
17
+ # ugh. copying and pasting code from ::ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference
18
+ index_options = options.delete(:index)
19
+ add_column(table_name, "#{ref_name}_id", :integer, options)
20
+ add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
21
+ add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
22
+
23
+ self
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,88 @@
1
+ module SchemaPlus::ForeignKeys
2
+ module Middleware
3
+ module Dumper
4
+
5
+ # index and foreign key constraint definitions are dumped
6
+ # inline in the create_table block. (This is done for elegance, but
7
+ # also because Sqlite3 does not allow foreign key constraints to be
8
+ # added to a table after it has been defined.)
9
+
10
+ module Tables
11
+
12
+ def before(env)
13
+
14
+ @inline_fks = Hash.new{ |h, k| h[k] = [] }
15
+ @backref_fks = Hash.new{ |h, k| h[k] = [] }
16
+
17
+ env.connection.tables.each do |table|
18
+ if (fks = env.connection.foreign_keys(table)).any?
19
+ env.dump.data.has_fks = true
20
+ @inline_fks[table] = fks
21
+ env.dump.depends(table, fks.collect(&:to_table))
22
+ end
23
+ end
24
+
25
+ # Normally we dump foreign key constraints inline in the table
26
+ # definitions, both for visual cleanliness and because sqlite3
27
+ # doesn't allow foreign key constraints to be added afterwards.
28
+ # But in case there's a cycle in the constraint references, some
29
+ # constraints will need to be broken out then added later. (Adding
30
+ # constraints later won't work with sqlite3, but that means sqlite3
31
+ # won't let you create cycles in the first place.)
32
+ break_fk_cycles(env) while env.dump.strongly_connected_components.any?{|component| component.size > 1}
33
+
34
+ env.dump.data.inline_fks = @inline_fks
35
+ env.dump.data.backref_fks = @backref_fks
36
+ end
37
+
38
+ # Ignore the foreign key dumps at the end of the schema; we'll put them in/near their tables
39
+ def after(env)
40
+ env.dump.final.reject!(&it =~/foreign_key/)
41
+ end
42
+
43
+ private
44
+
45
+ def break_fk_cycles(env) #:nodoc:
46
+ env.dump.strongly_connected_components.select{|component| component.size > 1}.each do |tables|
47
+ table = tables.sort.last
48
+ backref_fks = @inline_fks[table].select{|fk| tables.include?(fk.to_table)}
49
+ @inline_fks[table] -= backref_fks
50
+ env.dump.dependencies[table] -= backref_fks.collect(&:to_table)
51
+ backref_fks.each do |fk|
52
+ @backref_fks[fk.to_table] << fk
53
+ end
54
+ end
55
+ end
56
+
57
+ module SQLite3
58
+ def after(env)
59
+ env.dump.initial << " PRAGMA FOREIGN_KEYS = ON;\n" if env.dump.data.has_fks
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ module Table
66
+ def after(env)
67
+ dumped = {}
68
+ env.table.columns.each do |column|
69
+ if (foreign_key = env.dump.data.inline_fks[env.table.name].find(&its.column.to_s == column.name))
70
+ column.add_option foreign_key.to_dump(column: true)
71
+ dumped[foreign_key] = true
72
+ end
73
+ if (foreign_key = env.dump.data.backref_fks.values.flatten.find{|fk| fk.from_table.to_s == env.table.name && fk.column.to_s == column.name})
74
+ column.add_comment "foreign key references #{foreign_key.to_table.inspect} (below)"
75
+ end
76
+ end
77
+ env.table.trailer += env.dump.data.inline_fks[env.table.name].map { |foreign_key|
78
+ foreign_key.to_dump unless dumped[foreign_key] # just in case we missed any. don't think it can happen
79
+ }.compact.sort
80
+ env.table.trailer += env.dump.data.backref_fks[env.table.name].map { |foreign_key|
81
+ foreign_key.to_dump
82
+ }.sort
83
+ end
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,147 @@
1
+ module SchemaPlus::ForeignKeys
2
+ module Middleware
3
+ module Migration
4
+
5
+ module CreateTable
6
+ def around(env)
7
+ if (original_block = env.block)
8
+ config_options = env.options.delete(:foreign_keys) || {}
9
+ env.block = -> (table_definition) {
10
+ table_definition.schema_plus_foreign_keys_config = SchemaPlus::ForeignKeys.config.merge(config_options)
11
+ original_block.call table_definition
12
+ }
13
+ end
14
+ yield env
15
+ end
16
+ end
17
+
18
+ module Column
19
+
20
+ #
21
+ # Column option shortcuts
22
+ #
23
+ def before(env)
24
+ opts = env.options[:foreign_key]
25
+
26
+ return if opts == false
27
+
28
+ opts = {} if opts == true
29
+
30
+ [:references, :on_update, :on_delete, :deferrable].each do |key|
31
+ (opts||={}).reverse_merge!(key => env.options[key]) if env.options.has_key? key
32
+ end
33
+
34
+ return if opts.nil?
35
+
36
+ if opts.has_key?(:references) && !opts[:references]
37
+ env.options[:foreign_key] = false
38
+ return
39
+ end
40
+
41
+ case opts[:references]
42
+ when nil
43
+ when Array
44
+ table, primary_key = opts[:references]
45
+ opts[:references] = table
46
+ opts[:primary_key] ||= primary_key
47
+ end
48
+
49
+ env.options[:foreign_key] = opts
50
+ end
51
+
52
+ #
53
+ # Add the foreign keys
54
+ #
55
+ def around(env)
56
+ original_options = env.options
57
+ env.options = original_options.dup
58
+
59
+ is_reference = (env.type == :reference)
60
+ is_polymorphic = is_reference && env.options[:polymorphic]
61
+
62
+ # usurp foreign key creation from AR, since it doesn't support
63
+ # all our features
64
+ env.options[:foreign_key] = false
65
+
66
+ yield env
67
+
68
+ return if is_polymorphic or env.implements_reference
69
+
70
+ env.options = original_options
71
+
72
+ add_foreign_keys(env)
73
+
74
+ end
75
+
76
+ private
77
+
78
+ def add_foreign_keys(env)
79
+
80
+ if (reverting = env.caller.is_a?(::ActiveRecord::Migration::CommandRecorder) && env.caller.reverting)
81
+ commands_length = env.caller.commands.length
82
+ end
83
+
84
+ config = (env.caller.try(:schema_plus_foreign_keys_config) || SchemaPlus::ForeignKeys.config)
85
+ fk_opts = get_fk_opts(env, config)
86
+
87
+ # remove existing fk in case of change of fk on existing column
88
+ if env.operation == :change and fk_opts # includes :none for explicitly off
89
+ remove_foreign_key_if_exists(env)
90
+ end
91
+
92
+ fk_opts = nil if fk_opts == :none
93
+
94
+ create_fk(env, fk_opts) if fk_opts
95
+
96
+ if reverting
97
+ rev = []
98
+ while env.caller.commands.length > commands_length
99
+ cmd = env.caller.commands.pop
100
+ rev.unshift cmd unless cmd[0].to_s =~ /^add_/
101
+ end
102
+ env.caller.commands.concat rev
103
+ end
104
+
105
+ end
106
+
107
+ def create_fk(env, fk_opts)
108
+ references = fk_opts.delete(:references)
109
+ case env.caller
110
+ when ::ActiveRecord::ConnectionAdapters::TableDefinition
111
+ env.caller.foreign_key(env.column_name, references, fk_opts)
112
+ else
113
+ env.caller.add_foreign_key(env.table_name, references, fk_opts.merge(:column => env.column_name))
114
+ end
115
+ end
116
+
117
+ def get_fk_opts(env, config)
118
+ opts = env.options[:foreign_key]
119
+ return nil if opts.nil?
120
+ return :none if opts == false
121
+ opts = {} if opts == true
122
+ opts[:references] ||= default_table_name(env)
123
+ opts[:on_update] ||= config.on_update
124
+ opts[:on_delete] ||= config.on_delete
125
+ opts
126
+ end
127
+
128
+ def remove_foreign_key_if_exists(env)
129
+ env.caller.remove_foreign_key(env.table_name.to_s, column: env.column_name.to_s, :if_exists => true)
130
+ end
131
+
132
+ def default_table_name(env)
133
+ if env.column_name.to_s == 'parent_id'
134
+ env.table_name
135
+ else
136
+ name = env.column_name.to_s.sub(/_id$/, '')
137
+ name = name.pluralize if ::ActiveRecord::Base.pluralize_table_names
138
+ name
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+ end
146
+ end
147
+