active_record_inline_schema 0.4.0 → 0.5.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.
- data/CHANGELOG +53 -0
- data/Gemfile +24 -8
- data/README.md +8 -1
- data/active_record_inline_schema.gemspec +6 -5
- data/lib/active_record_inline_schema/active_record_class_methods.rb +27 -0
- data/lib/active_record_inline_schema/config/column.rb +35 -0
- data/lib/active_record_inline_schema/config/index.rb +44 -0
- data/lib/active_record_inline_schema/config.rb +219 -0
- data/lib/active_record_inline_schema/version.rb +1 -1
- data/lib/active_record_inline_schema.rb +10 -3
- data/spec/models.rb +11 -9
- data/spec/mysql_spec.rb +2 -2
- data/spec/postgresql_spec.rb +1 -3
- data/spec/shared_examples.rb +20 -70
- data/spec/spec_helper.rb +28 -3
- data/spec/sqlite3_spec.rb +0 -2
- metadata +47 -6
- data/lib/active_record_inline_schema/auto_schema.rb +0 -229
data/CHANGELOG
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
## 0.5.0 / 2012-04-19
|
2
|
+
|
3
|
+
* Known issues
|
4
|
+
|
5
|
+
* Custom inheritance columns will be ignored unless you have activerecord >3.2.3 (unreleased as of this writing). The bug is https://github.com/rails/rails/pull/5327
|
6
|
+
|
7
|
+
* Breaking changes
|
8
|
+
|
9
|
+
* the block form (schema {}) is removed
|
10
|
+
* .col only accepts one column at a time
|
11
|
+
* .col no longer accepts :as => :references
|
12
|
+
was: col :group, :as => :references
|
13
|
+
now: col :group_id, :type => :integer
|
14
|
+
* .col no longer accepts :index => true
|
15
|
+
was: col :foo, :index => true
|
16
|
+
now: col :foo and then add_index :foo
|
17
|
+
* A whole bunch of class methods are no longer mixed into ActiveRecord::Base (see Enhancements below)
|
18
|
+
|
19
|
+
* Enhancements
|
20
|
+
|
21
|
+
* Database connection no longer needs to be established before you define columns/indexes (but of course you have to connect before you auto_upgrade!)
|
22
|
+
* Only adds four (4) class methods to ActiveRecord::Base: .auto_upgrade!, .col, .add_index, and .inline_schema_config
|
23
|
+
* Tested on MRI 1.8, MRI 1.9, and JRuby 1.6.7+ across mysql, postgresql, and sqlite3
|
24
|
+
* Simply and DRY via a big refactor
|
25
|
+
|
26
|
+
## Difference between mini_record v0.2.1 and active_record_inline_schema v0.4.0
|
27
|
+
|
28
|
+
Having diverged at [this commit](https://github.com/DAddYE/mini_record/commit/55b2545f9772f7500d3782ac530b3da456f50023), I did stuff like...
|
29
|
+
|
30
|
+
* didn't set primary key on table definition
|
31
|
+
* allow custom column types
|
32
|
+
* removed aliases
|
33
|
+
* shorten name with zlib trick
|
34
|
+
* allow passing create_table_options
|
35
|
+
* default to ENGINE=InnoDB on mysql
|
36
|
+
* detect and create table for non-standard primary key
|
37
|
+
* move where schema inheritance columns are defined
|
38
|
+
* removed table_definition accessor
|
39
|
+
* allow custom column types like `varbinary`
|
40
|
+
* more compatible with ActiveRecord 3.0 and 3.2.2
|
41
|
+
|
42
|
+
## Difference between mini_record v0.2.1 and mini_record v0.3.0 (which I didn't fork)
|
43
|
+
|
44
|
+
After I made this list, I decided to diverge from mini_record v0.2.1 instead of doing a pull request:
|
45
|
+
|
46
|
+
* allow custom column types
|
47
|
+
* add timestamps
|
48
|
+
* remove unused (unmentioned) tables
|
49
|
+
* call auto_upgrade on descendant tables
|
50
|
+
* automagically generate fields from associations
|
51
|
+
* revised difference checking code
|
52
|
+
|
53
|
+
Pity the forker (me)!
|
data/Gemfile
CHANGED
@@ -1,10 +1,26 @@
|
|
1
|
-
source
|
2
|
-
|
3
|
-
gem "rake"
|
4
|
-
gem "minitest"
|
5
|
-
gem "sqlite3"
|
6
|
-
gem 'mysql'
|
7
|
-
gem 'sqlite3-ruby'
|
8
|
-
gem 'pg'
|
1
|
+
source :rubygems
|
9
2
|
|
10
3
|
gemspec
|
4
|
+
|
5
|
+
gem 'rake'
|
6
|
+
gem 'minitest'
|
7
|
+
gem 'minitest-reporters'
|
8
|
+
|
9
|
+
platforms :ruby do
|
10
|
+
# if RUBY_VERSION >= '1.9'
|
11
|
+
# gem 'debugger'
|
12
|
+
# else
|
13
|
+
# gem 'ruby-debug19'
|
14
|
+
# end
|
15
|
+
# gem 'mysql2', '~>0.2'
|
16
|
+
gem 'mysql2', '>=0.3'
|
17
|
+
gem 'sqlite3'
|
18
|
+
gem 'pg'
|
19
|
+
end
|
20
|
+
|
21
|
+
platforms :jruby do
|
22
|
+
gem 'jruby-openssl'
|
23
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
24
|
+
gem 'activerecord-jdbcmysql-adapter'
|
25
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
26
|
+
end
|
data/README.md
CHANGED
@@ -33,6 +33,9 @@ Lots and lots of use in the [`earth` library](https://github.com/brighterplanet/
|
|
33
33
|
Airport.auto_upgrade!
|
34
34
|
|
35
35
|
class ApiResponse < ActiveRecord::Base
|
36
|
+
# store with: self.body = Zlib::Deflate.deflate(body, Zlib::BEST_SPEED)
|
37
|
+
# retrieve with: Zlib::Inflate.inflate(raw_body).force_encoding 'UTF-8'
|
38
|
+
# just an idea!
|
36
39
|
col :raw_body, :type => 'varbinary(16384)'
|
37
40
|
end
|
38
41
|
ApiResponse.auto_upgrade!
|
@@ -43,8 +46,12 @@ Massive thanks to DAddYE, who you follow on twitter [@daddye](http://twitter.com
|
|
43
46
|
|
44
47
|
## TODO
|
45
48
|
|
46
|
-
* merge back into mini_record? they make some choices (automatically creating relationships, mixing in lots of aliases, etc.) that I don't need
|
47
49
|
* make the documentation as good as mini_record
|
50
|
+
* investigate switching back to ActiveRecord::ConnectionAdapters::TableDefinition as a way of holding column and index info
|
51
|
+
|
52
|
+
## History
|
53
|
+
|
54
|
+
Forked from [`mini_record` version v0.2.1](https://github.com/DAddYE/mini_record) - thanks @daddye! See CHANGELOG for a rough outline of the differences.
|
48
55
|
|
49
56
|
## Copyright
|
50
57
|
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require "active_record_inline_schema/version"
|
2
|
+
require File.expand_path("../lib/active_record_inline_schema/version", __FILE__)
|
4
3
|
|
5
4
|
Gem::Specification.new do |s|
|
6
5
|
s.name = "active_record_inline_schema"
|
@@ -16,10 +15,12 @@ Gem::Specification.new do |s|
|
|
16
15
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
16
|
s.require_paths = ["lib"]
|
18
17
|
|
18
|
+
s.add_runtime_dependency 'lock_method'
|
19
|
+
s.add_runtime_dependency 'activesupport'
|
19
20
|
s.add_runtime_dependency "activerecord", ">=3"
|
20
|
-
# s.add_runtime_dependency "activerecord", "3.0.
|
21
|
-
# s.add_runtime_dependency "activerecord", "3.1
|
22
|
-
# s.add_runtime_dependency "activerecord", "3.2
|
21
|
+
# s.add_runtime_dependency "activerecord", "~>3.0" # must use mysql2 ~>0.2 to test
|
22
|
+
# s.add_runtime_dependency "activerecord", "~>3.1"
|
23
|
+
# s.add_runtime_dependency "activerecord", "~>3.2"
|
23
24
|
|
24
25
|
# dev dependencies appear to be in the Gemfile
|
25
26
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module ActiveRecordInlineSchema::ActiveRecordClassMethods
|
4
|
+
MUTEX = ::Mutex.new
|
5
|
+
|
6
|
+
def inline_schema_config
|
7
|
+
if superclass != ::ActiveRecord::Base
|
8
|
+
return base_class.inline_schema_config
|
9
|
+
end
|
10
|
+
@inline_schema_config || MUTEX.synchronize do
|
11
|
+
@inline_schema_config ||= ::ActiveRecordInlineSchema::Config.new self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def col(column_name, options = {})
|
16
|
+
inline_schema_config.add_ideal_column column_name, options
|
17
|
+
end
|
18
|
+
|
19
|
+
# this is not a typo - specify column name, not index name
|
20
|
+
def add_index(column_name, options = {})
|
21
|
+
inline_schema_config.add_ideal_index column_name, options
|
22
|
+
end
|
23
|
+
|
24
|
+
def auto_upgrade!(create_table_options = nil)
|
25
|
+
inline_schema_config.apply create_table_options
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ActiveRecordInlineSchema::Config::Column
|
2
|
+
DEFAULT_TYPE = :string
|
3
|
+
|
4
|
+
attr_reader :parent
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :type
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize(parent, name, options)
|
10
|
+
@parent = parent
|
11
|
+
@name = name.to_s
|
12
|
+
options = options.symbolize_keys
|
13
|
+
if options.slice(:precision, :scale).keys.length == 1
|
14
|
+
raise ::ArgumentError, %{[active_record_inline_schema] :precision and :scale must always be specified together}
|
15
|
+
end
|
16
|
+
@type = options.fetch(:type, DEFAULT_TYPE).to_sym
|
17
|
+
@options = options.except :type, :name
|
18
|
+
end
|
19
|
+
|
20
|
+
def inject(table_definition)
|
21
|
+
if type != :primary_key and table_definition.respond_to?(type)
|
22
|
+
table_definition.send type, name, options
|
23
|
+
else
|
24
|
+
table_definition.column name, type, options
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
other.is_a?(self.class) and parent == other.parent and name == other.name and options == other.options
|
30
|
+
end
|
31
|
+
|
32
|
+
def hash
|
33
|
+
[parent, name, options].hash
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
class ActiveRecordInlineSchema::Config::Index
|
5
|
+
attr_reader :parent
|
6
|
+
attr_reader :column_name
|
7
|
+
attr_reader :initial_options
|
8
|
+
|
9
|
+
delegate :connection, :to => :parent
|
10
|
+
delegate :model, :to => :parent
|
11
|
+
|
12
|
+
def initialize(parent, column_name, options)
|
13
|
+
@parent = parent
|
14
|
+
@column_name = column_name.to_s
|
15
|
+
@initial_options = options.symbolize_keys
|
16
|
+
@name_mutex = ::Mutex.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
@name || @name_mutex.synchronize do
|
21
|
+
@name ||= begin
|
22
|
+
max_name_length = connection.index_name_length
|
23
|
+
prototype = connection.index_name model.table_name, :column => column_name
|
24
|
+
if prototype.length < max_name_length
|
25
|
+
prototype
|
26
|
+
else
|
27
|
+
prototype[0..(max_name_length-11)] + ::Zlib.crc32(prototype).to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def options
|
34
|
+
@initial_options.merge(:column_name => column_name, :name => name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def eql?(other)
|
38
|
+
other.is_a?(self.class) and parent == other.parent and column_name == other.column_name and initial_options == other.initial_options
|
39
|
+
end
|
40
|
+
|
41
|
+
def hash
|
42
|
+
[parent, column_name, initial_options].hash
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'lock_method'
|
3
|
+
|
4
|
+
class ActiveRecordInlineSchema::Config
|
5
|
+
DEFAULT_CREATE_TABLE_OPTIONS = {
|
6
|
+
:mysql => 'ENGINE=InnoDB'
|
7
|
+
}
|
8
|
+
|
9
|
+
attr_reader :model
|
10
|
+
attr_reader :ideal_columns
|
11
|
+
attr_reader :ideal_indexes
|
12
|
+
|
13
|
+
def initialize(model)
|
14
|
+
@model = model
|
15
|
+
@ideal_columns = ::Set.new
|
16
|
+
@ideal_indexes = ::Set.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_ideal_column(column_name, options)
|
20
|
+
ideal_columns.add Column.new(self, column_name, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_ideal_index(column_name, options)
|
24
|
+
ideal_indexes.add Index.new(self, column_name, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def apply(create_table_options)
|
28
|
+
non_standard_primary_key = if (primary_key_column = find_ideal_column(model.primary_key))
|
29
|
+
primary_key_column.type != :primary_key
|
30
|
+
end
|
31
|
+
|
32
|
+
unless non_standard_primary_key
|
33
|
+
add_ideal_column :id, :type => :primary_key
|
34
|
+
end
|
35
|
+
|
36
|
+
# Table doesn't exist, create it
|
37
|
+
unless connection.table_exists? model.table_name
|
38
|
+
|
39
|
+
if mysql?
|
40
|
+
create_table_options ||= DEFAULT_CREATE_TABLE_OPTIONS[database_type]
|
41
|
+
end
|
42
|
+
|
43
|
+
table_definition = ::ActiveRecord::ConnectionAdapters::TableDefinition.new connection
|
44
|
+
ideal_columns.each do |ideal_column|
|
45
|
+
ideal_column.inject table_definition
|
46
|
+
end
|
47
|
+
|
48
|
+
# avoid using connection.create_table because in 3.0.x it ignores table_definition
|
49
|
+
# and it also is too eager about adding a primary key column
|
50
|
+
create_sql = "CREATE TABLE #{model.quoted_table_name} (#{table_definition.to_sql}) #{create_table_options}"
|
51
|
+
|
52
|
+
if sqlite?
|
53
|
+
connection.execute create_sql
|
54
|
+
if non_standard_primary_key
|
55
|
+
add_ideal_index model.primary_key, :unique => true
|
56
|
+
end
|
57
|
+
elsif postgresql?
|
58
|
+
connection.execute create_sql
|
59
|
+
if non_standard_primary_key
|
60
|
+
# can't use add_index method because it won't let you do "PRIMARY KEY"
|
61
|
+
connection.execute "ALTER TABLE #{model.quoted_table_name} ADD PRIMARY KEY (#{model.quoted_primary_key})"
|
62
|
+
end
|
63
|
+
elsif mysql?
|
64
|
+
if non_standard_primary_key
|
65
|
+
# only string keys are supported
|
66
|
+
create_sql.sub! %r{#{connection.quote_column_name(model.primary_key)} varchar\(255\)([^,\)]*)}, "#{connection.quote_column_name(model.primary_key)} varchar(255)\\1 PRIMARY KEY"
|
67
|
+
create_sql.sub! 'DEFAULT NULLPRIMARY KEY', 'PRIMARY KEY'
|
68
|
+
end
|
69
|
+
connection.execute create_sql
|
70
|
+
end
|
71
|
+
safe_reset_column_information
|
72
|
+
end
|
73
|
+
|
74
|
+
# Add to schema inheritance column if necessary
|
75
|
+
if model.descendants.any? and not find_ideal_column(model.inheritance_column)
|
76
|
+
add_ideal_column model.inheritance_column, :type => :string
|
77
|
+
end
|
78
|
+
|
79
|
+
# Remove fields from db no longer in schema
|
80
|
+
existing_column_names.reject do |existing_column_name|
|
81
|
+
find_ideal_column existing_column_name
|
82
|
+
end.each do |existing_column_name|
|
83
|
+
connection.remove_column model.table_name, existing_column_name
|
84
|
+
end
|
85
|
+
|
86
|
+
# Add fields to db new to schema
|
87
|
+
ideal_columns.reject do |ideal_column|
|
88
|
+
find_existing_column ideal_column.name
|
89
|
+
end.each do |ideal_column|
|
90
|
+
connection.add_column model.table_name, ideal_column.name, ideal_column.type, ideal_column.options
|
91
|
+
end
|
92
|
+
|
93
|
+
# Change attributes of existent columns
|
94
|
+
existing_columns_hash.each do |existing_column_name, existing_column|
|
95
|
+
next if existing_column_name.to_s == model.primary_key.to_s
|
96
|
+
ideal_column = find_ideal_column existing_column_name
|
97
|
+
option_changes = {}
|
98
|
+
|
99
|
+
# First, check if the field type changed
|
100
|
+
type_changed = !([existing_column.type.to_s, existing_column.sql_type.to_s].include?(ideal_column.type.to_s))
|
101
|
+
|
102
|
+
# Next, iterate through our extended attributes, looking for any differences
|
103
|
+
# This catches stuff like :null, :precision, etc
|
104
|
+
ideal_column.options.except(:base).each do |k, v|
|
105
|
+
if !v.nil? and v != existing_column.send(k)
|
106
|
+
option_changes[k] = v
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Change the column if applicable
|
111
|
+
if type_changed or option_changes.any?
|
112
|
+
connection.change_column model.table_name, existing_column_name, ideal_column.type, option_changes
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Remove old index
|
117
|
+
existing_index_names.reject do |existing_index_name|
|
118
|
+
find_ideal_index existing_index_name
|
119
|
+
end.each do |existing_index_name|
|
120
|
+
connection.remove_index model.table_name, :name => existing_index_name
|
121
|
+
end
|
122
|
+
|
123
|
+
# Add indexes
|
124
|
+
ideal_indexes.reject do |ideal_index|
|
125
|
+
find_existing_index ideal_index.name
|
126
|
+
end.each do |ideal_index|
|
127
|
+
connection.add_index model.table_name, ideal_index.column_name, ideal_index.options
|
128
|
+
end
|
129
|
+
|
130
|
+
safe_reset_column_information
|
131
|
+
end
|
132
|
+
lock_method :apply, :ttl => 60
|
133
|
+
|
134
|
+
def as_lock
|
135
|
+
if connection.respond_to?(:current_database)
|
136
|
+
[connection.current_database, model.name]
|
137
|
+
else
|
138
|
+
model.name
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def clear
|
143
|
+
@ideal_columns = ::Set.new
|
144
|
+
@ideal_indexes = ::Set.new
|
145
|
+
end
|
146
|
+
lock_method :clear, :ttl => 60
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def find_ideal_column(name)
|
151
|
+
ideal_columns.detect { |ideal_column| ideal_column.name.to_s == name.to_s }
|
152
|
+
end
|
153
|
+
|
154
|
+
def find_existing_column(name)
|
155
|
+
existing_column_names.detect { |existing_column_name| existing_column_name.to_s == name.to_s }
|
156
|
+
end
|
157
|
+
|
158
|
+
def find_ideal_index(name)
|
159
|
+
ideal_indexes.detect { |ideal_index| ideal_index.name.to_s == name.to_s }
|
160
|
+
end
|
161
|
+
|
162
|
+
def find_existing_index(name)
|
163
|
+
existing_index_names.detect { |existing_index_name| existing_index_name.to_s == name.to_s }
|
164
|
+
end
|
165
|
+
|
166
|
+
def safe_reset_column_information
|
167
|
+
if connection.respond_to?(:schema_cache)
|
168
|
+
connection.schema_cache.clear!
|
169
|
+
end
|
170
|
+
model.reset_column_information
|
171
|
+
model.descendants.each do |descendant|
|
172
|
+
descendant.reset_column_information
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def existing_index_names
|
177
|
+
safe_reset_column_information
|
178
|
+
connection.indexes(model.table_name).map(&:name)
|
179
|
+
end
|
180
|
+
|
181
|
+
def existing_column_names
|
182
|
+
safe_reset_column_information
|
183
|
+
model.column_names
|
184
|
+
end
|
185
|
+
|
186
|
+
def existing_columns_hash
|
187
|
+
safe_reset_column_information
|
188
|
+
model.columns_hash
|
189
|
+
end
|
190
|
+
|
191
|
+
def connection
|
192
|
+
unless model.connection.active?
|
193
|
+
raise ::RuntimeError, %{[active_record_inline_schema] Must connect to database before running ActiveRecord::Base.auto_upgrade!}
|
194
|
+
end
|
195
|
+
model.connection
|
196
|
+
end
|
197
|
+
|
198
|
+
def database_type
|
199
|
+
if mysql?
|
200
|
+
:mysql
|
201
|
+
elsif postgresql?
|
202
|
+
:postgresql
|
203
|
+
elsif sqlite?
|
204
|
+
:sqlite
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def sqlite?
|
209
|
+
connection.adapter_name =~ /sqlite/i
|
210
|
+
end
|
211
|
+
|
212
|
+
def mysql?
|
213
|
+
connection.adapter_name =~ /mysql/i
|
214
|
+
end
|
215
|
+
|
216
|
+
def postgresql?
|
217
|
+
connection.adapter_name =~ /postgresql/i
|
218
|
+
end
|
219
|
+
end
|
@@ -1,5 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_support/core_ext'
|
2
2
|
require 'active_record'
|
3
|
-
require 'active_record_inline_schema/auto_schema'
|
4
3
|
|
5
|
-
|
4
|
+
require 'active_record_inline_schema/config'
|
5
|
+
require 'active_record_inline_schema/config/column'
|
6
|
+
require 'active_record_inline_schema/config/index'
|
7
|
+
require 'active_record_inline_schema/active_record_class_methods'
|
8
|
+
|
9
|
+
module ActiveRecordInlineSchema
|
10
|
+
end
|
11
|
+
|
12
|
+
ActiveRecord::Base.extend ActiveRecordInlineSchema::ActiveRecordClassMethods
|
data/spec/models.rb
CHANGED
@@ -2,9 +2,7 @@
|
|
2
2
|
|
3
3
|
class Person < ActiveRecord::Base
|
4
4
|
include SpecHelper
|
5
|
-
|
6
|
-
s.string :name
|
7
|
-
end
|
5
|
+
col :name
|
8
6
|
end
|
9
7
|
|
10
8
|
class Post < ActiveRecord::Base
|
@@ -12,7 +10,7 @@ class Post < ActiveRecord::Base
|
|
12
10
|
|
13
11
|
col :title
|
14
12
|
col :body
|
15
|
-
col :
|
13
|
+
col :category_id, :type => :integer
|
16
14
|
belongs_to :category
|
17
15
|
end
|
18
16
|
|
@@ -26,14 +24,16 @@ end
|
|
26
24
|
class Animal < ActiveRecord::Base
|
27
25
|
include SpecHelper
|
28
26
|
|
29
|
-
col :name
|
27
|
+
col :name
|
28
|
+
add_index :name
|
30
29
|
add_index :id
|
31
30
|
end
|
32
31
|
|
33
32
|
class Pet < ActiveRecord::Base
|
34
33
|
include SpecHelper
|
35
34
|
|
36
|
-
col :name
|
35
|
+
col :name
|
36
|
+
add_index :name
|
37
37
|
end
|
38
38
|
class Dog < Pet; end
|
39
39
|
class Cat < Pet; end
|
@@ -56,8 +56,8 @@ class Gender < ActiveRecord::Base
|
|
56
56
|
end
|
57
57
|
|
58
58
|
class User < ActiveRecord::Base
|
59
|
+
self.inheritance_column = 'role'
|
59
60
|
include SpecHelper
|
60
|
-
self.inheritance_column = 'role' # messed up in 3.2.2
|
61
61
|
col :name
|
62
62
|
col :surname
|
63
63
|
col :role
|
@@ -67,8 +67,10 @@ class Customer < User; end
|
|
67
67
|
|
68
68
|
class Fake < ActiveRecord::Base
|
69
69
|
include SpecHelper
|
70
|
-
col :name
|
71
|
-
col :
|
70
|
+
col :name
|
71
|
+
col :surname
|
72
|
+
col :category_id, :type => :integer
|
73
|
+
col :group_id, :type => :integer
|
72
74
|
end
|
73
75
|
|
74
76
|
class AutomobileMakeModelYearVariant < ActiveRecord::Base
|
data/spec/mysql_spec.rb
CHANGED
@@ -16,7 +16,7 @@ system %{#{cmd} -e "drop database #{database}"}
|
|
16
16
|
system %{#{cmd} -e "create database #{database}"}
|
17
17
|
|
18
18
|
ActiveRecord::Base.establish_connection(
|
19
|
-
'adapter' => 'mysql',
|
19
|
+
'adapter' => (RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2'),
|
20
20
|
'encoding' => 'utf8',
|
21
21
|
'database' => database,
|
22
22
|
'username' => username,
|
@@ -26,6 +26,6 @@ ActiveRecord::Base.establish_connection(
|
|
26
26
|
# require 'logger'
|
27
27
|
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
|
28
28
|
|
29
|
-
require File.expand_path('../models.rb', __FILE__)
|
29
|
+
# require File.expand_path('../models.rb', __FILE__)
|
30
30
|
|
31
31
|
require File.expand_path('../shared_examples.rb', __FILE__)
|
data/spec/postgresql_spec.rb
CHANGED
@@ -13,13 +13,11 @@ ActiveRecord::Base.establish_connection(
|
|
13
13
|
'adapter' => 'postgresql',
|
14
14
|
'encoding' => 'utf8',
|
15
15
|
'database' => database,
|
16
|
-
'username' => username
|
16
|
+
'username' => username
|
17
17
|
# 'password' => password
|
18
18
|
)
|
19
19
|
|
20
20
|
# require 'logger'
|
21
21
|
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
|
22
22
|
|
23
|
-
require File.expand_path('../models.rb', __FILE__)
|
24
|
-
|
25
23
|
require File.expand_path('../shared_examples.rb', __FILE__)
|
data/spec/shared_examples.rb
CHANGED
@@ -5,61 +5,9 @@ describe ActiveRecordInlineSchema do
|
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
8
|
-
it 'has #schema inside model' do
|
9
|
-
# For unknown reason separate specs doesn't works
|
10
|
-
ActiveRecord::Base.connection.table_exists?(Person.table_name).must_equal false
|
11
|
-
Person.auto_upgrade!
|
12
|
-
Person.table_name.must_equal 'people'
|
13
|
-
Person.db_columns.sort.must_equal %w[id name]
|
14
|
-
Person.column_names.sort.must_equal Person.db_columns
|
15
|
-
Person.column_names.sort.must_equal Person.schema_columns
|
16
|
-
person = Person.create(:name => 'foo')
|
17
|
-
person.name.must_equal 'foo'
|
18
|
-
proc { person.surname }.must_raise NoMethodError
|
19
|
-
|
20
|
-
# Add a column without lost data
|
21
|
-
Person.class_eval do
|
22
|
-
schema do |p|
|
23
|
-
p.string :name
|
24
|
-
p.string :surname
|
25
|
-
end
|
26
|
-
end
|
27
|
-
Person.auto_upgrade!
|
28
|
-
Person.count.must_equal 1
|
29
|
-
person = Person.last
|
30
|
-
person.name.must_equal 'foo'
|
31
|
-
person.surname.must_be_nil
|
32
|
-
person.update_attribute(:surname, 'bar')
|
33
|
-
Person.db_columns.sort.must_equal %w[id name surname]
|
34
|
-
Person.column_names.sort.must_equal Person.db_columns
|
35
|
-
|
36
|
-
# Remove a column without lost data
|
37
|
-
Person.class_eval do
|
38
|
-
schema do |p|
|
39
|
-
p.string :name
|
40
|
-
end
|
41
|
-
end
|
42
|
-
Person.auto_upgrade!
|
43
|
-
person = Person.last
|
44
|
-
person.name.must_equal 'foo'
|
45
|
-
proc { person.surname }.must_raise NoMethodError
|
46
|
-
Person.db_columns.sort.must_equal %w[id name]
|
47
|
-
Person.column_names.sort.must_equal Person.db_columns
|
48
|
-
Person.column_names.sort.must_equal Person.schema_columns
|
49
|
-
|
50
|
-
# Change column without lost data
|
51
|
-
Person.class_eval do
|
52
|
-
schema do |p|
|
53
|
-
p.text :name
|
54
|
-
end
|
55
|
-
end
|
56
|
-
person = Person.last
|
57
|
-
person.name.must_equal 'foo'
|
58
|
-
end
|
59
|
-
|
60
8
|
it 'has #key,col,property,attribute inside model' do
|
61
|
-
ActiveRecord::Base.connection.table_exists?(Post.table_name).must_equal false
|
62
|
-
ActiveRecord::Base.connection.table_exists?(Category.table_name).must_equal false
|
9
|
+
(!!ActiveRecord::Base.connection.table_exists?(Post.table_name)).must_equal false
|
10
|
+
(!!ActiveRecord::Base.connection.table_exists?(Category.table_name)).must_equal false
|
63
11
|
Post.auto_upgrade!; Category.auto_upgrade!
|
64
12
|
Post.column_names.sort.must_equal Post.db_columns
|
65
13
|
Category.column_names.sort.must_equal Category.schema_columns
|
@@ -74,10 +22,10 @@ describe ActiveRecordInlineSchema do
|
|
74
22
|
|
75
23
|
|
76
24
|
# Remove a column
|
77
|
-
Post.
|
25
|
+
Post.inline_schema_config.clear
|
78
26
|
Post.class_eval do
|
79
27
|
col :name
|
80
|
-
col :
|
28
|
+
col :category_id, :type => :integer
|
81
29
|
end
|
82
30
|
Post.auto_upgrade!
|
83
31
|
post = Post.first
|
@@ -90,19 +38,21 @@ describe ActiveRecordInlineSchema do
|
|
90
38
|
# Check indexes
|
91
39
|
Animal.auto_upgrade!
|
92
40
|
Animal.db_indexes.size.must_be :>, 0
|
93
|
-
Animal.db_indexes.must_equal Animal.
|
41
|
+
Animal.db_indexes.must_equal Animal.schema_indexes
|
94
42
|
|
95
43
|
indexes_was = Animal.db_indexes
|
96
44
|
|
97
45
|
# Remove an index
|
98
|
-
|
46
|
+
target = indexes_was.pop
|
47
|
+
Animal.inline_schema_config.ideal_indexes.delete_if { |ideal_index| ideal_index.name.to_s == target.to_s }
|
99
48
|
Animal.auto_upgrade!
|
100
|
-
Animal.
|
49
|
+
Animal.schema_indexes.sort.must_equal indexes_was
|
101
50
|
Animal.db_indexes.must_equal indexes_was
|
102
51
|
|
103
52
|
# Add a new index
|
104
53
|
Animal.class_eval do
|
105
|
-
col :
|
54
|
+
col :category_id, :type => :integer
|
55
|
+
add_index :category_id
|
106
56
|
end
|
107
57
|
Animal.auto_upgrade!
|
108
58
|
Animal.db_columns.must_include "category_id"
|
@@ -111,7 +61,7 @@ describe ActiveRecordInlineSchema do
|
|
111
61
|
|
112
62
|
it 'works with STI' do
|
113
63
|
Pet.auto_upgrade!
|
114
|
-
Pet.
|
64
|
+
Pet.safe_reset_column_information
|
115
65
|
Pet.db_columns.must_include "type"
|
116
66
|
Dog.auto_upgrade!
|
117
67
|
Pet.db_columns.must_include "type"
|
@@ -129,8 +79,7 @@ describe ActiveRecordInlineSchema do
|
|
129
79
|
Dog.first.name.must_equal "bar"
|
130
80
|
|
131
81
|
# What's happen if we change schema?
|
132
|
-
Dog.
|
133
|
-
Dog.indexes.must_equal Pet.indexes
|
82
|
+
Dog.schema_indexes.must_equal Pet.schema_indexes
|
134
83
|
Dog.class_eval do
|
135
84
|
col :bau
|
136
85
|
end
|
@@ -142,6 +91,7 @@ describe ActiveRecordInlineSchema do
|
|
142
91
|
|
143
92
|
it 'works with custom inheritance column' do
|
144
93
|
User.auto_upgrade!
|
94
|
+
User.inheritance_column.must_equal 'role' # known to fail on rails 3
|
145
95
|
Administrator.create(:name => "Davide", :surname => "D'Agostino")
|
146
96
|
Customer.create(:name => "Foo", :surname => "Bar")
|
147
97
|
Administrator.count.must_equal 1
|
@@ -149,8 +99,8 @@ describe ActiveRecordInlineSchema do
|
|
149
99
|
Customer.count.must_equal 1
|
150
100
|
Customer.first.name.must_equal "Foo"
|
151
101
|
User.count.must_equal 2
|
152
|
-
User.
|
153
|
-
User.
|
102
|
+
User.find_by_name('Davide').role.must_equal "Administrator"
|
103
|
+
User.find_by_name('Foo').role.must_equal "Customer"
|
154
104
|
end
|
155
105
|
|
156
106
|
it 'allow multiple columns definitions' do
|
@@ -202,17 +152,17 @@ describe ActiveRecordInlineSchema do
|
|
202
152
|
it 'is idempotent' do
|
203
153
|
ActiveRecord::Base.descendants.each do |active_record|
|
204
154
|
active_record.auto_upgrade!
|
205
|
-
active_record.
|
155
|
+
active_record.safe_reset_column_information
|
206
156
|
before = [ active_record.db_columns, active_record.db_indexes ]
|
207
157
|
active_record.auto_upgrade!
|
208
|
-
active_record.
|
158
|
+
active_record.safe_reset_column_information
|
209
159
|
[ active_record.db_columns, active_record.db_indexes ].must_equal before
|
210
160
|
active_record.auto_upgrade!
|
211
|
-
active_record.
|
161
|
+
active_record.safe_reset_column_information
|
212
162
|
active_record.auto_upgrade!
|
213
|
-
active_record.
|
163
|
+
active_record.safe_reset_column_information
|
214
164
|
active_record.auto_upgrade!
|
215
|
-
active_record.
|
165
|
+
active_record.safe_reset_column_information
|
216
166
|
[ active_record.db_columns, active_record.db_indexes ].must_equal before
|
217
167
|
end
|
218
168
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,19 @@
|
|
1
|
-
require 'rubygems'
|
1
|
+
require 'rubygems'
|
2
2
|
require 'bundler/setup'
|
3
|
-
|
3
|
+
|
4
|
+
if ::Bundler.definition.specs['debugger'].first
|
5
|
+
require 'debugger'
|
6
|
+
elsif ::Bundler.definition.specs['ruby-debug'].first
|
7
|
+
require 'ruby-debug'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'minitest/spec'
|
4
11
|
require 'minitest/autorun'
|
12
|
+
require 'minitest/reporters'
|
13
|
+
MiniTest::Unit.runner = MiniTest::SuiteRunner.new
|
14
|
+
MiniTest::Unit.runner.reporters << MiniTest::Reporters::SpecReporter.new
|
15
|
+
|
16
|
+
require 'active_record_inline_schema'
|
5
17
|
|
6
18
|
# require 'logger'
|
7
19
|
# ActiveRecord::Base.logger = Logger.new($stderr)
|
@@ -22,7 +34,20 @@ module SpecHelper
|
|
22
34
|
end
|
23
35
|
|
24
36
|
def schema_columns
|
25
|
-
|
37
|
+
inline_schema_config.ideal_columns.map { |c| c.name.to_s }.sort
|
38
|
+
end
|
39
|
+
|
40
|
+
def schema_indexes
|
41
|
+
inline_schema_config.ideal_indexes.map { |c| c.name.to_s }.sort
|
42
|
+
end
|
43
|
+
|
44
|
+
def safe_reset_column_information
|
45
|
+
if connection.respond_to?(:schema_cache)
|
46
|
+
connection.schema_cache.clear!
|
47
|
+
end
|
48
|
+
reset_column_information
|
26
49
|
end
|
27
50
|
end
|
28
51
|
end
|
52
|
+
|
53
|
+
require File.expand_path('../models.rb', __FILE__)
|
data/spec/sqlite3_spec.rb
CHANGED
@@ -5,6 +5,4 @@ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':me
|
|
5
5
|
# require 'logger'
|
6
6
|
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
|
7
7
|
|
8
|
-
require File.expand_path('../models.rb', __FILE__)
|
9
|
-
|
10
8
|
require File.expand_path('../shared_examples.rb', __FILE__)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_inline_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,43 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-04-19 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: lock_method
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: activesupport
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
15
47
|
- !ruby/object:Gem::Dependency
|
16
48
|
name: activerecord
|
17
|
-
requirement:
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
18
50
|
none: false
|
19
51
|
requirements:
|
20
52
|
- - ! '>='
|
@@ -22,7 +54,12 @@ dependencies:
|
|
22
54
|
version: '3'
|
23
55
|
type: :runtime
|
24
56
|
prerelease: false
|
25
|
-
version_requirements:
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '3'
|
26
63
|
description: Specify columns like you would with ActiveRecord migrations and then
|
27
64
|
run .auto_upgrade! Based on the mini_record gem from Davide D'Agostino, it adds
|
28
65
|
fewer aliases, doesn't create timestamps and relationship columns automatically.
|
@@ -35,12 +72,16 @@ extra_rdoc_files: []
|
|
35
72
|
files:
|
36
73
|
- .gitignore
|
37
74
|
- .travis.yml
|
75
|
+
- CHANGELOG
|
38
76
|
- Gemfile
|
39
77
|
- README.md
|
40
78
|
- Rakefile
|
41
79
|
- active_record_inline_schema.gemspec
|
42
80
|
- lib/active_record_inline_schema.rb
|
43
|
-
- lib/active_record_inline_schema/
|
81
|
+
- lib/active_record_inline_schema/active_record_class_methods.rb
|
82
|
+
- lib/active_record_inline_schema/config.rb
|
83
|
+
- lib/active_record_inline_schema/config/column.rb
|
84
|
+
- lib/active_record_inline_schema/config/index.rb
|
44
85
|
- lib/active_record_inline_schema/version.rb
|
45
86
|
- spec/models.rb
|
46
87
|
- spec/mysql_spec.rb
|
@@ -68,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
109
|
version: '0'
|
69
110
|
requirements: []
|
70
111
|
rubyforge_project:
|
71
|
-
rubygems_version: 1.8.
|
112
|
+
rubygems_version: 1.8.21
|
72
113
|
signing_key:
|
73
114
|
specification_version: 3
|
74
115
|
summary: Define table structure (columns and indexes) inside your ActiveRecord models
|
@@ -1,229 +0,0 @@
|
|
1
|
-
require 'zlib'
|
2
|
-
module ActiveRecordInlineSchema
|
3
|
-
module AutoSchema
|
4
|
-
def self.included(base)
|
5
|
-
base.extend(ClassMethods)
|
6
|
-
end
|
7
|
-
|
8
|
-
module ClassMethods
|
9
|
-
|
10
|
-
def table_definition
|
11
|
-
return superclass.table_definition unless superclass == ActiveRecord::Base
|
12
|
-
|
13
|
-
@_table_definition ||= begin
|
14
|
-
ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def indexes
|
19
|
-
return superclass.indexes unless superclass == ActiveRecord::Base
|
20
|
-
|
21
|
-
@_indexes ||= {}
|
22
|
-
end
|
23
|
-
|
24
|
-
def col(*args)
|
25
|
-
return unless connection?
|
26
|
-
|
27
|
-
options = args.extract_options!
|
28
|
-
type = options.delete(:as) || options.delete(:type) || :string
|
29
|
-
args.each do |column_name|
|
30
|
-
if table_definition.respond_to?(type)
|
31
|
-
table_definition.send(type, column_name, options)
|
32
|
-
else
|
33
|
-
table_definition.column(column_name, type, options)
|
34
|
-
end
|
35
|
-
column_name = table_definition.columns[-1].name
|
36
|
-
case index_name = options.delete(:index)
|
37
|
-
when Hash
|
38
|
-
add_index(options.delete(:column) || column_name, index_name)
|
39
|
-
when TrueClass
|
40
|
-
add_index(column_name)
|
41
|
-
when String, Symbol, Array
|
42
|
-
add_index(index_name)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def reset_table_definition!
|
48
|
-
@_table_definition = nil
|
49
|
-
end
|
50
|
-
|
51
|
-
def schema
|
52
|
-
reset_table_definition!
|
53
|
-
yield table_definition
|
54
|
-
table_definition
|
55
|
-
end
|
56
|
-
|
57
|
-
def add_index(column_name, options={})
|
58
|
-
index_name = shorten_index_name connection.index_name(table_name, :column => column_name)
|
59
|
-
indexes[index_name] = options.merge(:column => column_name, :name => index_name)
|
60
|
-
index_name
|
61
|
-
end
|
62
|
-
|
63
|
-
def connection?
|
64
|
-
!!connection
|
65
|
-
rescue Exception => e
|
66
|
-
puts "\e[31m%s\e[0m" % e.message.strip
|
67
|
-
false
|
68
|
-
end
|
69
|
-
|
70
|
-
def shorten_index_name(name)
|
71
|
-
if name.length < connection.index_name_length
|
72
|
-
name
|
73
|
-
else
|
74
|
-
name[0..(connection.index_name_length-11)] + ::Zlib.crc32(name).to_s
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def sqlite?
|
79
|
-
connection.adapter_name =~ /sqlite/i
|
80
|
-
end
|
81
|
-
|
82
|
-
def mysql?
|
83
|
-
connection.adapter_name =~ /mysql/i
|
84
|
-
end
|
85
|
-
|
86
|
-
def postgresql?
|
87
|
-
connection.adapter_name =~ /postgresql/i
|
88
|
-
end
|
89
|
-
|
90
|
-
def auto_upgrade!(create_table_options = '')
|
91
|
-
return unless connection?
|
92
|
-
|
93
|
-
# normally activerecord's mysql adapter does this
|
94
|
-
if mysql?
|
95
|
-
create_table_options ||= 'ENGINE=InnoDB'
|
96
|
-
end
|
97
|
-
|
98
|
-
non_standard_primary_key = if (primary_key_column = table_definition.columns.detect { |column| column.name.to_s == primary_key.to_s })
|
99
|
-
primary_key_column.type != :primary_key
|
100
|
-
end
|
101
|
-
|
102
|
-
unless non_standard_primary_key
|
103
|
-
table_definition.column :id, :primary_key
|
104
|
-
end
|
105
|
-
|
106
|
-
# Table doesn't exist, create it
|
107
|
-
unless connection.table_exists? table_name
|
108
|
-
|
109
|
-
# avoid using connection.create_table because in 3.0.x it ignores table_definition
|
110
|
-
# and it also is too eager about adding a primary key column
|
111
|
-
create_sql = "CREATE TABLE #{quoted_table_name} (#{table_definition.to_sql}) #{create_table_options}"
|
112
|
-
|
113
|
-
if sqlite?
|
114
|
-
connection.execute create_sql
|
115
|
-
if non_standard_primary_key
|
116
|
-
add_index primary_key, :unique => true
|
117
|
-
end
|
118
|
-
elsif postgresql?
|
119
|
-
connection.execute create_sql
|
120
|
-
if non_standard_primary_key
|
121
|
-
# can't use add_index method because it won't let you do "PRIMARY KEY"
|
122
|
-
connection.execute "ALTER TABLE #{quoted_table_name} ADD PRIMARY KEY (#{quoted_primary_key})"
|
123
|
-
end
|
124
|
-
elsif mysql?
|
125
|
-
if non_standard_primary_key
|
126
|
-
# only string keys are supported
|
127
|
-
create_sql.sub! %r{#{connection.quote_column_name(primary_key)} varchar\(255\)([^,\)]*)}, "#{connection.quote_column_name(primary_key)} varchar(255)\\1 PRIMARY KEY"
|
128
|
-
create_sql.sub! 'DEFAULT NULLPRIMARY KEY', 'PRIMARY KEY'
|
129
|
-
end
|
130
|
-
connection.execute create_sql
|
131
|
-
end
|
132
|
-
|
133
|
-
if connection.respond_to?(:schema_cache)
|
134
|
-
connection.schema_cache.clear!
|
135
|
-
end
|
136
|
-
reset_column_information
|
137
|
-
end
|
138
|
-
|
139
|
-
# Add to schema inheritance column if necessary
|
140
|
-
if descendants.present? && !table_definition.columns.any? { |column| column.name.to_s == inheritance_column.to_s }
|
141
|
-
table_definition.column inheritance_column, :string
|
142
|
-
end
|
143
|
-
|
144
|
-
# Grab database columns
|
145
|
-
fields_in_db = connection.columns(table_name).inject({}) do |hash, column|
|
146
|
-
hash[column.name] = column
|
147
|
-
hash
|
148
|
-
end
|
149
|
-
|
150
|
-
# Grab new schema
|
151
|
-
fields_in_schema = table_definition.columns.inject({}) do |hash, column|
|
152
|
-
hash[column.name.to_s] = column
|
153
|
-
hash
|
154
|
-
end
|
155
|
-
|
156
|
-
# Remove fields from db no longer in schema
|
157
|
-
(fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
|
158
|
-
column = fields_in_db[field]
|
159
|
-
connection.remove_column table_name, column.name
|
160
|
-
end
|
161
|
-
|
162
|
-
# Add fields to db new to schema
|
163
|
-
(fields_in_schema.keys - fields_in_db.keys).each do |field|
|
164
|
-
column = fields_in_schema[field]
|
165
|
-
options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
|
166
|
-
options[:default] = column.default if !column.default.nil?
|
167
|
-
options[:null] = column.null if !column.null.nil?
|
168
|
-
connection.add_column table_name, column.name, column.type.to_sym, options
|
169
|
-
end
|
170
|
-
|
171
|
-
# Change attributes of existent columns
|
172
|
-
(fields_in_schema.keys & fields_in_db.keys).each do |field|
|
173
|
-
if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
|
174
|
-
changed = false # flag
|
175
|
-
new_type = fields_in_schema[field].type.to_sym
|
176
|
-
new_attr = {}
|
177
|
-
|
178
|
-
# First, check if the field type changed
|
179
|
-
if (fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym) and (fields_in_schema[field].type.to_sym != fields_in_db[field].sql_type.to_sym)
|
180
|
-
# $stderr.puts "A(#{field}) - #{fields_in_schema[field].type.to_sym}"
|
181
|
-
# $stderr.puts "B(#{field}) - #{fields_in_db[field].type.to_sym} - #{fields_in_db[field].sql_type.to_sym}"
|
182
|
-
changed = true
|
183
|
-
end
|
184
|
-
|
185
|
-
# Special catch for precision/scale, since *both* must be specified together
|
186
|
-
# Always include them in the attr struct, but they'll only get applied if changed = true
|
187
|
-
new_attr[:precision] = fields_in_schema[field][:precision]
|
188
|
-
new_attr[:scale] = fields_in_schema[field][:scale]
|
189
|
-
|
190
|
-
# Next, iterate through our extended attributes, looking for any differences
|
191
|
-
# This catches stuff like :null, :precision, etc
|
192
|
-
fields_in_schema[field].each_pair do |att,value|
|
193
|
-
next if att == :type or att == :base or att == :name # special cases
|
194
|
-
if !value.nil? && value != fields_in_db[field].send(att)
|
195
|
-
# $stderr.puts "C(#{att}) - #{value.inspect}"
|
196
|
-
# $stderr.puts "D(#{att}) - #{fields_in_db[field].send(att).inspect}"
|
197
|
-
new_attr[att] = value
|
198
|
-
changed = true
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Change the column if applicable
|
203
|
-
connection.change_column table_name, field, new_type, new_attr if changed
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
# Remove old index
|
208
|
-
indexes_in_db = connection.indexes(table_name).map(&:name)
|
209
|
-
(indexes_in_db - indexes.keys).each do |name|
|
210
|
-
connection.remove_index(table_name, :name => name)
|
211
|
-
end
|
212
|
-
|
213
|
-
# Add indexes
|
214
|
-
indexes.each do |name, options|
|
215
|
-
options = options.dup
|
216
|
-
unless connection.indexes(table_name).detect { |i| i.name == name }
|
217
|
-
connection.add_index(table_name, options.delete(:column), options)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# Reload column information
|
222
|
-
if connection.respond_to?(:schema_cache)
|
223
|
-
connection.schema_cache.clear!
|
224
|
-
end
|
225
|
-
reset_column_information
|
226
|
-
end
|
227
|
-
end # ClassMethods
|
228
|
-
end # AutoSchema
|
229
|
-
end # ActiveRecordInlineSchema
|