schema_plus 1.8.9 → 2.0.0.pre1

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -4
  3. data/.travis.yml +1 -47
  4. data/CHANGELOG.md +0 -35
  5. data/README.md +73 -107
  6. data/Rakefile +7 -10
  7. data/TODO.md +51 -0
  8. data/gemfiles/Gemfile.base +2 -0
  9. data/lib/schema_column_plus.rb +7 -0
  10. data/lib/{schema_plus → schema_column_plus}/active_record/connection_adapters/column.rb +13 -11
  11. data/lib/schema_column_plus/middleware/model.rb +22 -0
  12. data/lib/schema_db_default.rb +13 -0
  13. data/lib/{schema_plus → schema_db_default}/active_record/attribute.rb +4 -4
  14. data/lib/schema_db_default/db_default.rb +17 -0
  15. data/lib/schema_db_default/middleware.rb +30 -0
  16. data/lib/schema_default_expr.rb +32 -0
  17. data/lib/schema_default_expr/active_record/connection_adapters/mysql_adapter.rb +17 -0
  18. data/lib/schema_default_expr/active_record/connection_adapters/postgresql_adapter.rb +18 -0
  19. data/lib/schema_default_expr/active_record/connection_adapters/sqlite3_adapter.rb +35 -0
  20. data/lib/schema_default_expr/middleware.rb +54 -0
  21. data/lib/schema_pg_enums.rb +6 -0
  22. data/lib/schema_pg_enums/active_record.rb +69 -0
  23. data/lib/schema_pg_enums/middleware.rb +23 -0
  24. data/lib/schema_plus.rb +17 -45
  25. data/lib/schema_plus/active_record/base.rb +6 -23
  26. data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +80 -181
  27. data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +78 -99
  28. data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +34 -114
  29. data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +16 -370
  30. data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +1 -67
  31. data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +18 -112
  32. data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +14 -116
  33. data/lib/schema_plus/active_record/migration/command_recorder.rb +8 -59
  34. data/lib/schema_plus/middleware/dumper.rb +94 -0
  35. data/lib/schema_plus/middleware/migration.rb +167 -0
  36. data/lib/schema_plus/middleware/model.rb +17 -0
  37. data/lib/schema_plus/version.rb +1 -1
  38. data/lib/schema_plus_tables.rb +15 -0
  39. data/lib/schema_plus_tables/active_record/connection_adapters/abstract_adapter.rb +20 -0
  40. data/lib/schema_plus_tables/active_record/connection_adapters/mysql_adapter.rb +25 -0
  41. data/lib/schema_plus_tables/active_record/connection_adapters/postgresql_adapter.rb +13 -0
  42. data/lib/schema_plus_tables/active_record/connection_adapters/sqlite3_adapter.rb +12 -0
  43. data/lib/schema_views.rb +16 -0
  44. data/lib/schema_views/active_record/connection_adapters/abstract_adapter.rb +41 -0
  45. data/lib/schema_views/active_record/connection_adapters/mysql_adapter.rb +30 -0
  46. data/lib/schema_views/active_record/connection_adapters/postgresql_adapter.rb +31 -0
  47. data/lib/schema_views/active_record/connection_adapters/sqlite3_adapter.rb +18 -0
  48. data/lib/schema_views/middleware.rb +47 -0
  49. data/schema_dev.yml +1 -31
  50. data/schema_plus.gemspec +11 -9
  51. data/spec/foreign_key_definition_spec.rb +7 -7
  52. data/spec/foreign_key_spec.rb +63 -48
  53. data/spec/migration_spec.rb +58 -203
  54. data/spec/named_schemas_spec.rb +5 -88
  55. data/spec/{column_spec.rb → schema_column_plus/column_spec.rb} +26 -48
  56. data/spec/schema_db_default/column_spec.rb +58 -0
  57. data/spec/{column_default_spec.rb → schema_default_expr/column_default_spec.rb} +1 -2
  58. data/spec/schema_default_expr/schema_dumper_spec.rb +116 -0
  59. data/spec/schema_dumper_spec.rb +22 -327
  60. data/spec/{enum_spec.rb → schema_pg_enums/enum_spec.rb} +1 -1
  61. data/spec/schema_pg_enums/schema_dumper_spec.rb +37 -0
  62. data/spec/schema_views/named_schemas_spec.rb +97 -0
  63. data/spec/{views_spec.rb → schema_views/views_spec.rb} +1 -1
  64. data/spec/spec_helper.rb +2 -1
  65. data/spec/support/matchers/reference.rb +11 -12
  66. metadata +104 -57
  67. data/gemfiles/rails-3.2/Gemfile.base +0 -3
  68. data/gemfiles/rails-3.2/Gemfile.mysql +0 -10
  69. data/gemfiles/rails-3.2/Gemfile.mysql2 +0 -10
  70. data/gemfiles/rails-3.2/Gemfile.postgresql +0 -10
  71. data/gemfiles/rails-3.2/Gemfile.sqlite3 +0 -10
  72. data/gemfiles/rails-4.0/Gemfile.base +0 -3
  73. data/gemfiles/rails-4.0/Gemfile.mysql2 +0 -10
  74. data/gemfiles/rails-4.0/Gemfile.postgresql +0 -10
  75. data/gemfiles/rails-4.0/Gemfile.sqlite3 +0 -10
  76. data/gemfiles/rails-4.1/Gemfile.base +0 -3
  77. data/gemfiles/rails-4.1/Gemfile.mysql2 +0 -10
  78. data/gemfiles/rails-4.1/Gemfile.postgresql +0 -10
  79. data/gemfiles/rails-4.1/Gemfile.sqlite3 +0 -10
  80. data/lib/schema_plus/active_record/column_options_handler.rb +0 -117
  81. data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +0 -70
  82. data/lib/schema_plus/active_record/db_default.rb +0 -19
  83. data/lib/schema_plus/active_record/foreign_keys.rb +0 -137
  84. data/lib/schema_plus/active_record/schema_dumper.rb +0 -171
  85. data/lib/schema_plus/railtie.rb +0 -20
  86. data/spec/index_definition_spec.rb +0 -211
  87. data/spec/index_spec.rb +0 -249
@@ -0,0 +1,94 @@
1
+ module SchemaPlus
2
+ module Middleware
3
+ module Dumper
4
+
5
+ def self.insert
6
+ SchemaMonkey::Middleware::Dumper::Tables.prepend FkDependencies
7
+ SchemaMonkey::Middleware::Dumper::Tables.append IgnoreActiveRecordFkDumps
8
+ SchemaMonkey::Middleware::Dumper::Table.append ForeignKeys
9
+ end
10
+
11
+ # index and foreign key constraint definitions are dumped
12
+ # inline in the create_table block. (This is done for elegance, but
13
+ # also because Sqlite3 does not allow foreign key constraints to be
14
+ # added to a table after it has been defined.)
15
+
16
+ #
17
+ # Middleware for the collection of tables
18
+ #
19
+
20
+ class FkDependencies < SchemaMonkey::Middleware::Base
21
+
22
+ def call(env)
23
+ @inline_fks = Hash.new{ |h, k| h[k] = [] }
24
+ @backref_fks = Hash.new{ |h, k| h[k] = [] }
25
+
26
+ env.connection.tables.each do |table|
27
+ @inline_fks[table] = env.connection.foreign_keys(table)
28
+ env.dump.depends(table, @inline_fks[table].collect(&:to_table))
29
+ end
30
+
31
+ # Normally we dump foreign key constraints inline in the table
32
+ # definitions, both for visual cleanliness and because sqlite3
33
+ # doesn't allow foreign key constraints to be added afterwards.
34
+ # But in case there's a cycle in the constraint references, some
35
+ # constraints will need to be broken out then added later. (Adding
36
+ # constraints later won't work with sqlite3, but that means sqlite3
37
+ # won't let you create cycles in the first place.)
38
+ break_fk_cycles(env) while env.dump.strongly_connected_components.any?{|component| component.size > 1}
39
+
40
+ env.dump.data.inline_fks = @inline_fks
41
+ env.dump.data.backref_fks = @backref_fks
42
+
43
+ continue env
44
+ end
45
+
46
+ def break_fk_cycles(env) #:nodoc:
47
+ env.dump.strongly_connected_components.select{|component| component.size > 1}.each do |tables|
48
+ table = tables.sort.first
49
+ backref_fks = @inline_fks[table].select{|fk| tables.include?(fk.to_table)}
50
+ @inline_fks[table] -= backref_fks
51
+ env.dump.dependencies[table] -= backref_fks.collect(&:to_table)
52
+ backref_fks.each do |fk|
53
+ @backref_fks[fk.to_table] << fk
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ class IgnoreActiveRecordFkDumps < SchemaMonkey::Middleware::Base
60
+ # Ignore the foreign key dumps at the end of the schema; we'll put them in/near their tables
61
+ def call(env)
62
+ continue env
63
+ env.dump.foreign_keys = []
64
+ end
65
+ end
66
+
67
+ #
68
+ # Middleware for individual tables
69
+ #
70
+ class ForeignKeys < SchemaMonkey::Middleware::Base
71
+ def call(env)
72
+ continue env
73
+ dumped = {}
74
+ env.table.columns.each do |column|
75
+ if (foreign_key = env.dump.data.inline_fks[env.table.name].find(&its.column.to_s == column.name))
76
+ column.add_option foreign_key.to_dump(column: true)
77
+ dumped[foreign_key] = true
78
+ end
79
+ 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})
80
+ column.add_comment "foreign key references #{foreign_key.to_table.inspect} (below)"
81
+ end
82
+ end
83
+ env.table.statements += env.dump.data.inline_fks[env.table.name].map { |foreign_key|
84
+ foreign_key.to_dump(inline: true) unless dumped[foreign_key]
85
+ }.compact.sort
86
+ env.table.trailer += env.dump.data.backref_fks[env.table.name].map { |foreign_key|
87
+ foreign_key.to_dump
88
+ }.sort
89
+ end
90
+ end
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,167 @@
1
+ module SchemaPlus
2
+ module Middleware
3
+ module Migration
4
+
5
+ def self.insert
6
+ SchemaMonkey::Middleware::Migration::Column.prepend Shortcuts
7
+ SchemaMonkey::Middleware::Migration::Column.append AddForeignKeys
8
+ end
9
+
10
+ class Shortcuts < SchemaMonkey::Middleware::Base
11
+ def call(env)
12
+ fk_options = env.options[:foreign_key]
13
+
14
+ case fk_options
15
+ when false then ;
16
+ when true then fk_options = {}
17
+ end
18
+
19
+ if fk_options != false # may be nil
20
+ [:references, :on_update, :on_delete, :deferrable].each do |key|
21
+ (fk_options||={}).reverse_merge!(key => env.options[key]) if env.options.has_key? key
22
+ end
23
+ end
24
+
25
+ if fk_options and fk_options.has_key?(:references)
26
+ case fk_options[:references]
27
+ when nil, false
28
+ fk_options = false
29
+ when Array then
30
+ table, primary_key = fk_options[:references]
31
+ fk_options[:references] = table
32
+ fk_options[:primary_key] ||= primary_key
33
+ end
34
+ end
35
+
36
+
37
+ fk_options = false if fk_options and fk_options.has_key?(:references) and not fk_options[:references]
38
+
39
+ env.options[:foreign_key] = fk_options
40
+
41
+ continue env
42
+
43
+ end
44
+ end
45
+
46
+ class AddForeignKeys < SchemaMonkey::Middleware::Base
47
+ def call(env)
48
+ options = env.options
49
+ original_options = options.dup
50
+
51
+ is_reference = (env.type == :reference)
52
+ is_polymorphic = is_reference && options[:polymorphic]
53
+
54
+ # usurp index creation from AR. That's necessary to make
55
+ # auto_index work properly
56
+ index = options.delete(:index) unless is_polymorphic
57
+ options[:foreign_key] = false if is_reference
58
+
59
+ continue env
60
+
61
+ return if is_polymorphic
62
+
63
+ env.options = original_options
64
+
65
+ add_foreign_keys_and_auto_index(env)
66
+
67
+ end
68
+
69
+ def add_foreign_keys_and_auto_index(env)
70
+
71
+ if (reverting = env.caller.is_a?(::ActiveRecord::Migration::CommandRecorder) && env.caller.reverting)
72
+ commands_length = env.caller.commands.length
73
+ end
74
+
75
+ config = (env.caller.try(:schema_plus_config) || SchemaPlus.config).foreign_keys
76
+ fk_args = get_fk_args(env, config)
77
+
78
+ # remove existing fk and auto-generated index in case of change of fk on existing column
79
+ if env.operation == :change and fk_args # includes :none for explicitly off
80
+ remove_foreign_key_if_exists(env)
81
+ remove_auto_index_if_exists(env)
82
+ end
83
+
84
+ fk_args = nil if fk_args == :none
85
+
86
+ create_index(env, fk_args, config)
87
+ create_fk(env, fk_args) if fk_args
88
+
89
+ if reverting
90
+ rev = []
91
+ while env.caller.commands.length > commands_length
92
+ cmd = env.caller.commands.pop
93
+ rev.unshift cmd unless cmd[0].to_s =~ /^add_/
94
+ end
95
+ env.caller.commands.concat rev
96
+ end
97
+
98
+ end
99
+
100
+ def auto_index_name(env)
101
+ ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.auto_index_name(env.table_name, env.column_name)
102
+ end
103
+
104
+ def create_index(env, fk_args, config)
105
+ # create index if requested explicity or implicitly due to auto_index
106
+ index = env.options[:index]
107
+ index = { :name => auto_index_name(env) } if index.nil? and fk_args && config.auto_index?
108
+ return unless index
109
+ case env.caller
110
+ when ::ActiveRecord::ConnectionAdapters::TableDefinition
111
+ env.caller.index(env.column_name, index)
112
+ else
113
+ env.caller.add_index(env.table_name, env.column_name, index)
114
+ end
115
+ end
116
+
117
+ def create_fk(env, fk_args)
118
+ references = fk_args.delete(:references)
119
+ case env.caller
120
+ when ::ActiveRecord::ConnectionAdapters::TableDefinition
121
+ env.caller.foreign_key(env.column_name, references, fk_args)
122
+ else
123
+ env.caller.add_foreign_key(env.table_name, references, fk_args.merge(:column => env.column_name))
124
+ end
125
+ end
126
+
127
+
128
+ def get_fk_args(env, config)
129
+ args = nil
130
+ column_name = env.column_name.to_s
131
+ options = env.options
132
+
133
+ return :none if options[:foreign_key] == false
134
+
135
+ args = options[:foreign_key]
136
+ args ||= {} if config.auto_create? and column_name =~ /_id$/
137
+
138
+ return nil if args.nil?
139
+
140
+ args[:references] ||= env.table_name if column_name == 'parent_id'
141
+
142
+ args[:references] ||= begin
143
+ table_name = column_name.sub(/_id$/, '')
144
+ table_name = table_name.pluralize if ::ActiveRecord::Base.pluralize_table_names
145
+ table_name
146
+ end
147
+
148
+ args[:on_update] ||= config.on_update
149
+ args[:on_delete] ||= config.on_delete
150
+
151
+ args
152
+ end
153
+
154
+ def remove_foreign_key_if_exists(env)
155
+ env.caller.remove_foreign_key(env.table_name.to_s, column: env.column_name.to_s, :if_exists => true)
156
+ end
157
+
158
+ def remove_auto_index_if_exists(env)
159
+ env.caller.remove_index(env.table_name, :name => auto_index_name(env), :column => env.column_name, :if_exists => true)
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+ end
166
+ end
167
+
@@ -0,0 +1,17 @@
1
+ module SchemaPlus
2
+ module Middleware
3
+ module Model
4
+
5
+ def self.insert
6
+ SchemaMonkey::Middleware::Model::ResetColumnInformation.append ResetColumnInformation
7
+ end
8
+
9
+ class ResetColumnInformation < SchemaMonkey::Middleware::Base
10
+ def call(env)
11
+ continue env
12
+ env.model.reset_foreign_key_information
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module SchemaPlus
2
- VERSION = "1.8.9"
2
+ VERSION = "2.0.0.pre1"
3
3
  end
@@ -0,0 +1,15 @@
1
+ require 'schema_monkey'
2
+
3
+ require_relative 'schema_plus_tables/active_record/connection_adapters/abstract_adapter'
4
+
5
+ module SchemaPlusTables
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ autoload :MysqlAdapter, 'schema_plus_tables/active_record/connection_adapters/mysql_adapter'
9
+ autoload :PostgresqlAdapter, 'schema_plus_tables/active_record/connection_adapters/postgresql_adapter'
10
+ autoload :Sqlite3Adapter, 'schema_plus_tables/active_record/connection_adapters/sqlite3_adapter'
11
+ end
12
+ end
13
+ end
14
+
15
+ SchemaMonkey.register(SchemaPlusTables)
@@ -0,0 +1,20 @@
1
+ module SchemaPlusTables
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module AbstractAdapter
5
+
6
+ # Extends rails' drop_table to include these options:
7
+ # :cascade
8
+ # :if_exists
9
+ #
10
+ def drop_table(name, options = {})
11
+ sql = "DROP TABLE"
12
+ sql += " IF EXISTS" if options[:if_exists]
13
+ sql += " #{quote_table_name(name)}"
14
+ sql += " CASCADE" if options[:cascade]
15
+ execute sql
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ module SchemaPlusTables
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module MysqlAdapter
5
+
6
+ # implement cascade by removing foreign keys
7
+ def drop_table(name, options={})
8
+ if options[:cascade]
9
+ reverse_foreign_keys(name).each do |foreign_key|
10
+ remove_foreign_key(foreign_key.from_table, name: foreign_key.name)
11
+ end
12
+ end
13
+
14
+ sql = 'DROP'
15
+ sql += ' TEMPORARY' if options[:temporary]
16
+ sql += ' TABLE'
17
+ sql += ' IF EXISTS' if options[:if_exists]
18
+ sql += " #{quote_table_name(name)}"
19
+
20
+ execute sql
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module SchemaPlusTables
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module PostgresqlAdapter
5
+ # pg gem defines a drop_table with fewer options than our Abstract
6
+ # one, so use the abstract one instead
7
+ def drop_table(name, options={})
8
+ SchemaPlusTables::ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_method(:drop_table).bind(self).call(name, options)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module SchemaPlusTables
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module Sqlite3Adapter
5
+
6
+ def drop_table(name, options={})
7
+ super(name, options.except(:cascade))
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require 'schema_monkey'
2
+
3
+ require_relative 'schema_views/active_record/connection_adapters/abstract_adapter'
4
+ require_relative 'schema_views/middleware'
5
+
6
+ module SchemaViews
7
+ module ActiveRecord
8
+ module ConnectionAdapters
9
+ autoload :MysqlAdapter, 'schema_views/active_record/connection_adapters/mysql_adapter'
10
+ autoload :PostgresqlAdapter, 'schema_views/active_record/connection_adapters/postgresql_adapter'
11
+ autoload :Sqlite3Adapter, 'schema_views/active_record/connection_adapters/sqlite3_adapter'
12
+ end
13
+ end
14
+ end
15
+
16
+ SchemaMonkey.register(SchemaViews)
@@ -0,0 +1,41 @@
1
+ module SchemaViews
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module AbstractAdapter
5
+ # Create a view given the SQL definition. Specify :force => true
6
+ # to first drop the view if it already exists.
7
+ def create_view(view_name, definition, options={})
8
+ definition = definition.to_sql if definition.respond_to? :to_sql
9
+ if options[:force]
10
+ drop_view(view_name, if_exists: true)
11
+ end
12
+ execute "CREATE VIEW #{quote_table_name(view_name)} AS #{definition}"
13
+ end
14
+
15
+ # Drop the named view. Specify :if_exists => true
16
+ # to fail silently if the view doesn't exist.
17
+ def drop_view(view_name, options = {})
18
+ sql = "DROP VIEW"
19
+ sql += " IF EXISTS" if options[:if_exists]
20
+ sql += " #{quote_table_name(view_name)}"
21
+ execute sql
22
+ end
23
+
24
+ #####################################################################
25
+ #
26
+ # The functions below here are abstract; each subclass should
27
+ # define them all. Defining them here only for reference.
28
+ #
29
+
30
+ # (abstract) Returns the names of all views, as an array of strings
31
+ def views(name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; [] end
32
+
33
+ # (abstract) Returns the SQL definition of a given view. This is
34
+ # the literal SQL would come after 'CREATVE VIEW viewname AS ' in
35
+ # the SQL statement to create a view.
36
+ def view_definition(view_name, name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,30 @@
1
+ module SchemaViews
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module MysqlAdapter
5
+
6
+ def views(name = nil)
7
+ views = []
8
+ select_all("SELECT table_name FROM information_schema.views WHERE table_schema = SCHEMA()", name).each do |row|
9
+ views << row["table_name"]
10
+ end
11
+ views
12
+ end
13
+
14
+ def view_definition(view_name, name = nil)
15
+ results = select_all("SELECT view_definition, check_option FROM information_schema.views WHERE table_schema = SCHEMA() AND table_name = #{quote(view_name)}", name)
16
+ return nil unless results.any?
17
+ row = results.first
18
+ sql = row["view_definition"]
19
+ sql.gsub!(%r{#{quote_table_name(current_database)}[.]}, '')
20
+ case row["check_option"]
21
+ when "CASCADED" then sql += " WITH CASCADED CHECK OPTION"
22
+ when "LOCAL" then sql += " WITH LOCAL CHECK OPTION"
23
+ end
24
+ sql
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end