mini_record-cj 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.travis.yml +12 -0
- data/Gemfile +16 -0
- data/README.md +198 -0
- data/Rakefile +30 -0
- data/lib/mini_record.rb +5 -0
- data/lib/mini_record/auto_schema.rb +364 -0
- data/lib/mini_record/version.rb +3 -0
- data/mini_record.gemspec +28 -0
- data/test/helper.rb +69 -0
- data/test/models.rb +74 -0
- data/test/test_mini_record.rb +621 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 27896303273b7579110fcaca700f5ad8573606f6
|
4
|
+
data.tar.gz: 411456a93af4970469f7f7d7d4f5d9012518016a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6d6ae3f5c75e2041ad1787ca7ff2a4b0d69b46133c46c3a12b7fee9608f05a9c6e6f6bffb298adc29ce85abd4e8f5b7a16a395d21f7c42d77e7ee52e7c29034b
|
7
|
+
data.tar.gz: 4ee3159eee1475baa6764fc7e4f9781208222b94ca1f05a384255f40735a9e3d0542b690876d062f1346281217590a0051c46fc4b0a02c3814e3d1bb679ab9e8
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in mini_record.gemspec
|
4
|
+
gem 'rake'
|
5
|
+
gem 'minitest'
|
6
|
+
gem 'sqlite3'
|
7
|
+
gem 'mysql2'
|
8
|
+
gem 'mysql'
|
9
|
+
gem 'pg'
|
10
|
+
gem 'activerecord', '<= 3.2'
|
11
|
+
|
12
|
+
group :test do
|
13
|
+
gem 'foreigner', '>= 1.4.2'
|
14
|
+
end
|
15
|
+
|
16
|
+
gemspec
|
data/README.md
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
[![Build Status](https://secure.travis-ci.org/DAddYE/mini_record.png)](http://travis-ci.org/DAddYE/mini_record)
|
2
|
+
|
3
|
+
|
4
|
+
MiniRecord is a micro extension for our `ActiveRecord` gem.
|
5
|
+
With MiniRecord you can add the ability to create columns outside the default `schema.rb`, directly
|
6
|
+
in your **model** in a similar way that should know in others projects
|
7
|
+
like DataMapper, MongoMapper or MongoID.
|
8
|
+
|
9
|
+
My inspiration come from this handy [project](https://github.com/pjhyett/auto_migrations).
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
* Define columns/properties inside your model
|
14
|
+
* Perform migrations automatically
|
15
|
+
* Auto upgrade your schema, so if you know what you are doing you don't lose your existing data!
|
16
|
+
* Add, Remove, Change Columns; Add, Remove, Change indexes
|
17
|
+
|
18
|
+
## Instructions
|
19
|
+
|
20
|
+
What you need is to move/remove your `db/schema.rb`.
|
21
|
+
This avoid conflicts.
|
22
|
+
|
23
|
+
Add to your `Gemfile`:
|
24
|
+
|
25
|
+
``` rb
|
26
|
+
gem 'mini_record'
|
27
|
+
```
|
28
|
+
|
29
|
+
That's all!
|
30
|
+
|
31
|
+
## Examples
|
32
|
+
|
33
|
+
Remember that inside properties you can use all migrations methods,
|
34
|
+
see [documentation](http://api.rubyonrails.org/classes/ActiveRecord/Migration.html)
|
35
|
+
|
36
|
+
``` rb
|
37
|
+
class Post < ActiveRecord::Base
|
38
|
+
col :title_en, :title_jp
|
39
|
+
col :description_en, :description_jp, :as => :text
|
40
|
+
col :permalink, :index => true, :limit => 50
|
41
|
+
col :comments_count, :as => :integer
|
42
|
+
col :category, :as => :references, :index => true
|
43
|
+
end
|
44
|
+
Post.auto_upgrade!
|
45
|
+
```
|
46
|
+
|
47
|
+
If you don't like `col` there are also few aliases: `key, field, property, attribute`
|
48
|
+
|
49
|
+
Instead of `:as => :my_type` you can use `:type => :my_type`
|
50
|
+
|
51
|
+
Option `:as` or `:type` if not provided is `:string` by default, you can use all ActiveRecord types:
|
52
|
+
|
53
|
+
``` rb
|
54
|
+
:primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time,
|
55
|
+
:date, :binary, :boolean, :references, :belongs_to, :timestamp
|
56
|
+
```
|
57
|
+
|
58
|
+
You can provide others ActiveRecord options like:
|
59
|
+
|
60
|
+
``` rb
|
61
|
+
:limit, :default, :null, :precision, :scale
|
62
|
+
|
63
|
+
# example
|
64
|
+
class Foo < ActiveRecord::Base
|
65
|
+
col :title, :default => "MyTitle" # :as => :string is by default
|
66
|
+
col :price, :as => :decimal, :scale => 8, :precision => 2
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
See [ActiveRecord::TableDefinition](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html)
|
71
|
+
for more details.
|
72
|
+
|
73
|
+
Finally, when you execute `MyModel.auto_upgrade!`, missing columns, indexes and tables will be created on the fly.
|
74
|
+
Indexes and columns present in the db but **not** in your model schema will be **deleted*** also in your db.
|
75
|
+
|
76
|
+
### Single Table Inheritance
|
77
|
+
|
78
|
+
MiniRecord as ActiveRecord support STI plus some goodness, see our specs for more details.
|
79
|
+
|
80
|
+
### ActiveRecord Relations
|
81
|
+
|
82
|
+
MiniRecord has built-in support of belongs_to, belongs_to polymorphic and habtm relations. Just declaring these in your model will generate the necessary id columns, indexes and join tables
|
83
|
+
|
84
|
+
#### belongs_to
|
85
|
+
```ruby
|
86
|
+
class Address < ActiveRecord::Base
|
87
|
+
belongs_to :person
|
88
|
+
end
|
89
|
+
```
|
90
|
+
Will result in a person_id column (you can override with the `foreign_key` option) which is indexed
|
91
|
+
|
92
|
+
#### belongs_to with foreign key in database
|
93
|
+
```ruby
|
94
|
+
class Address < ActiveRecord::Base
|
95
|
+
belongs_to :person
|
96
|
+
index :person_id, :foreign => true
|
97
|
+
end
|
98
|
+
```
|
99
|
+
The same as in the previous case, but foreign key will be added to the database with help of [foreigner](https://github.com/matthuhiggins/foreigner) gem.
|
100
|
+
|
101
|
+
To remove the key please use :foreign => false
|
102
|
+
If you simple remove the index, the foreign key will not be removed.
|
103
|
+
|
104
|
+
#### belongs_to (polymorphic)
|
105
|
+
```ruby
|
106
|
+
class Address < ActiveRecord::Base
|
107
|
+
belongs_to :addressable, :polymorphic => true
|
108
|
+
end
|
109
|
+
```
|
110
|
+
Will result in addressable id and type columns with composite indexes `add_index(:addresses), [:addressable_id, :addressable_type]`
|
111
|
+
|
112
|
+
#### habtm
|
113
|
+
```ruby
|
114
|
+
class Address < ActiveRecord::Base
|
115
|
+
has_and_belongs_to_many :people
|
116
|
+
end
|
117
|
+
```
|
118
|
+
Will generate a "addresses_people" join table and index the id columns
|
119
|
+
|
120
|
+
### Adding a new column
|
121
|
+
|
122
|
+
Super easy, open your model and just add it:
|
123
|
+
|
124
|
+
``` rb
|
125
|
+
class Post < ActiveRecord::Base
|
126
|
+
col :title
|
127
|
+
col :body, :as => :text # <<- this
|
128
|
+
col :permalink, :index => true
|
129
|
+
col :comments_count, :as => :integer
|
130
|
+
col :category, :as => :references, :index => true
|
131
|
+
end
|
132
|
+
Post.auto_upgrade!
|
133
|
+
```
|
134
|
+
|
135
|
+
So now when you invoke `MyModel.auto_upgrade!` you should see a SQL query like `ALTER TABLE` that mean that your existing
|
136
|
+
records are happy and safe.
|
137
|
+
|
138
|
+
### Removing a column
|
139
|
+
|
140
|
+
It's exactly the same, but the column will be _really_ deleted without affect other columns.
|
141
|
+
|
142
|
+
### Change columns
|
143
|
+
|
144
|
+
It's not possible for us know when/what column you have renamed, but we can know if you changed the `type` so
|
145
|
+
if you change `t.string :name` to `t.text :name` we are be able to perform an `ALTER TABLE`
|
146
|
+
|
147
|
+
### Add/Remove indexes
|
148
|
+
|
149
|
+
In the same ways we manage columns MiniRecord will detect new indexes and indexes that needs to be removed.
|
150
|
+
So when you perform `MyModel.auto_upgrade!` a SQL command like:
|
151
|
+
|
152
|
+
``` SQL
|
153
|
+
PRAGMA index_info('index_people_on_name')
|
154
|
+
CREATE INDEX "index_people_on_surname" ON "people" ("surname")
|
155
|
+
```
|
156
|
+
|
157
|
+
Note that writing it in DSL way you have same options as `add_index` so you are be able to write:
|
158
|
+
|
159
|
+
``` rb
|
160
|
+
class Fox < ActiveRecord::Base
|
161
|
+
col :foo, :index => true
|
162
|
+
col :foo, :index => :custom_name
|
163
|
+
col :foo, :index => [:foo, :bar]
|
164
|
+
col :foo, :index => { :column => [:branch_id, :party_id], :unique => true, :name => 'by_branch_party' }
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
That is the same of:
|
169
|
+
|
170
|
+
``` rb
|
171
|
+
class Fox < ActiveRecord::Base
|
172
|
+
col :foo
|
173
|
+
add_index :foo
|
174
|
+
add_index :custom_name
|
175
|
+
add_index [:foo, :bar]
|
176
|
+
add_index [:branch_id, :party_id], :unique => true, :name => 'by_branch_party'
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
## Author
|
181
|
+
|
182
|
+
DAddYE, you can follow me on twitter [@daddye](http://twitter.com/daddye) or take a look at my site [daddye.it](http://www.daddye.it)
|
183
|
+
|
184
|
+
## Copyright
|
185
|
+
|
186
|
+
Copyright (C) 2011 Davide D'Agostino - [@daddye](http://twitter.com/daddye)
|
187
|
+
|
188
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
189
|
+
associated documentation files (the “Software”), to deal in the Software without restriction, including without
|
190
|
+
limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
191
|
+
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
192
|
+
|
193
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
194
|
+
|
195
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
196
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM,
|
197
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
198
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
%w(install release).each do |task|
|
7
|
+
Rake::Task[task].enhance do
|
8
|
+
sh "rm -rf pkg"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Bump version on github"
|
13
|
+
task :bump do
|
14
|
+
if `git status -s`.strip == ""
|
15
|
+
puts "\e[31mNothing to commit (working directory clean)\e[0m"
|
16
|
+
else
|
17
|
+
version = Bundler.load_gemspec(Dir[File.expand_path('../*.gemspec', __FILE__)].first).version
|
18
|
+
sh "git add .; git commit -a -m \"Bump to version #{version}\""
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
task :release => :bump
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'test'
|
25
|
+
test.test_files = Dir['test/**/test_*.rb']
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => :test
|
30
|
+
task :spec => :test
|
data/lib/mini_record.rb
ADDED
@@ -0,0 +1,364 @@
|
|
1
|
+
module MiniRecord
|
2
|
+
module AutoSchema
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def init_table_definition(connection)
|
10
|
+
#connection.create_table(table_name) unless connection.table_exists?(table_name)
|
11
|
+
|
12
|
+
case ActiveRecord::ConnectionAdapters::TableDefinition.instance_method(:initialize).arity
|
13
|
+
when 1
|
14
|
+
# Rails 3.2 and earlier
|
15
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
|
16
|
+
when 4, -5
|
17
|
+
# Rails 4
|
18
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.new(connection.native_database_types, table_name, false, {})
|
19
|
+
else
|
20
|
+
raise ArgumentError,
|
21
|
+
"Unsupported number of args for ActiveRecord::ConnectionAdapters::TableDefinition.new()"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def schema_tables
|
27
|
+
@@_schema_tables ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def table_definition
|
31
|
+
return superclass.table_definition unless (superclass == ActiveRecord::Base) || (superclass.respond_to?(:abstract_class?) && superclass.abstract_class?)
|
32
|
+
|
33
|
+
@_table_definition ||= begin
|
34
|
+
tb = init_table_definition(connection)
|
35
|
+
tb.primary_key(primary_key)
|
36
|
+
tb
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def indexes
|
41
|
+
return superclass.indexes unless (superclass == ActiveRecord::Base) || (superclass.respond_to?(:abstract_class?) && superclass.abstract_class?)
|
42
|
+
|
43
|
+
@_indexes ||= {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def indexes_in_db
|
47
|
+
connection.indexes(table_name).inject({}) do |hash, index|
|
48
|
+
hash[index.name] = index
|
49
|
+
hash
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_sql_field_type(field)
|
54
|
+
if field.respond_to?(:sql_type)
|
55
|
+
# Rails 3.2 and earlier
|
56
|
+
field.sql_type.to_s.downcase
|
57
|
+
else
|
58
|
+
# Rails 4
|
59
|
+
connection.type_to_sql(field.type.to_sym, field.limit, field.precision, field.scale)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def fields
|
64
|
+
table_definition.columns.inject({}) do |hash, column|
|
65
|
+
hash[column.name] = column
|
66
|
+
hash
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def fields_in_db
|
71
|
+
connection.columns(table_name).inject({}) do |hash, column|
|
72
|
+
hash[column.name] = column
|
73
|
+
hash
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def columns_hash
|
78
|
+
if mini_record_fake_columns
|
79
|
+
super.merge mini_record_fake_columns
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def mini_record_columns
|
86
|
+
@@_mr_columns ||= {}
|
87
|
+
@@_mr_columns[table_name] ||= {}
|
88
|
+
end
|
89
|
+
|
90
|
+
def mini_record_fake_columns
|
91
|
+
@@_mr_fake_columns ||= {}
|
92
|
+
@@_mr_fake_columns[table_name] ||= {}
|
93
|
+
end
|
94
|
+
|
95
|
+
def field(*args)
|
96
|
+
return unless connection?
|
97
|
+
|
98
|
+
options = args.extract_options!
|
99
|
+
type = options.delete(:as) || options.delete(:type) || :string
|
100
|
+
index = options.delete(:index)
|
101
|
+
fake = options.delete(:fake) || false
|
102
|
+
|
103
|
+
args.each do |column_name|
|
104
|
+
# add it it the mini record columns for this table so we can access
|
105
|
+
# special fields like input_as, used by form builders
|
106
|
+
mini_record_columns[column_name] = options
|
107
|
+
|
108
|
+
if fake
|
109
|
+
# allow you to access the field on the instance object
|
110
|
+
attr_accessor column_name
|
111
|
+
# create a column that column_hashes will understand (a fake row)
|
112
|
+
fake_column = ActiveRecord::ConnectionAdapters::Column.new(
|
113
|
+
column_name.to_s, nil, type, true
|
114
|
+
)
|
115
|
+
# add it to the list of fake columns for this table
|
116
|
+
mini_record_fake_columns[column_name.to_s] = fake_column
|
117
|
+
# skip everything else as it's a fake column and don't want it in the db
|
118
|
+
next
|
119
|
+
end
|
120
|
+
|
121
|
+
# Allow custom types like:
|
122
|
+
# t.column :type, "ENUM('EMPLOYEE','CLIENT','SUPERUSER','DEVELOPER')"
|
123
|
+
if type.is_a?(String)
|
124
|
+
# will be converted in: t.column :type, "ENUM('EMPLOYEE','CLIENT')"
|
125
|
+
table_definition.column(column_name, type, options.reverse_merge(:limit => 0))
|
126
|
+
else
|
127
|
+
# wil be converted in: t.string :name
|
128
|
+
table_definition.send(type, column_name, options)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get the correct column_name i.e. in field :category, :as => :references
|
132
|
+
column_name = table_definition.columns[-1].name
|
133
|
+
|
134
|
+
# Parse indexes
|
135
|
+
case index
|
136
|
+
when Hash
|
137
|
+
add_index(options.delete(:column) || column_name, index)
|
138
|
+
when TrueClass
|
139
|
+
add_index(column_name)
|
140
|
+
when String, Symbol, Array
|
141
|
+
add_index(index)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
alias :key :field
|
146
|
+
alias :property :field
|
147
|
+
alias :col :field
|
148
|
+
|
149
|
+
def timestamps
|
150
|
+
field :created_at, :updated_at, :as => :datetime, :null => false
|
151
|
+
end
|
152
|
+
|
153
|
+
def reset_table_definition!
|
154
|
+
@_table_definition = nil
|
155
|
+
end
|
156
|
+
alias :reset_schema! :reset_table_definition!
|
157
|
+
|
158
|
+
def schema
|
159
|
+
reset_table_definition!
|
160
|
+
yield table_definition
|
161
|
+
table_definition
|
162
|
+
end
|
163
|
+
|
164
|
+
def add_index(column_name, options={})
|
165
|
+
index_name = connection.index_name(table_name, :column => column_name)
|
166
|
+
indexes[index_name] = options.merge(:column => column_name) unless indexes.key?(index_name)
|
167
|
+
index_name
|
168
|
+
end
|
169
|
+
alias :index :add_index
|
170
|
+
|
171
|
+
def connection?
|
172
|
+
!!connection
|
173
|
+
rescue Exception => e
|
174
|
+
puts "\e[31m%s\e[0m" % e.message.strip
|
175
|
+
false
|
176
|
+
end
|
177
|
+
|
178
|
+
def clear_tables!
|
179
|
+
(connection.tables - schema_tables).each do |name|
|
180
|
+
connection.drop_table(name)
|
181
|
+
schema_tables.delete(name)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def foreign_keys
|
186
|
+
# fk cache to minimize quantity of sql queries
|
187
|
+
@foreign_keys ||= {}
|
188
|
+
@foreign_keys[:table_name] ||= connection.foreign_keys(table_name)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Remove foreign keys for indexes with :foreign=>false option
|
192
|
+
def remove_foreign_keys
|
193
|
+
indexes.each do |name, options|
|
194
|
+
if options[:foreign]==false
|
195
|
+
foreign_key = foreign_keys.detect { |fk| fk.options[:column] == options[:column].to_s }
|
196
|
+
if foreign_key
|
197
|
+
connection.remove_foreign_key(table_name, :name => foreign_key.options[:name])
|
198
|
+
foreign_keys.delete(foreign_key)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Add foreign keys for indexes with :foreign=>true option, if the key doesn't exists
|
205
|
+
def add_foreign_keys
|
206
|
+
indexes.each do |name, options|
|
207
|
+
if options[:foreign]
|
208
|
+
column = options[:column].to_s
|
209
|
+
unless foreign_keys.detect { |fk| fk[:options][:column] == column }
|
210
|
+
to_table = reflect_on_all_associations.detect { |a| a.foreign_key.to_s==column }.table_name
|
211
|
+
connection.add_foreign_key(table_name, to_table, options)
|
212
|
+
foreign_keys << { :options=> { :column=>column } }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def auto_upgrade!
|
219
|
+
return unless connection?
|
220
|
+
return if respond_to?(:abstract_class?) && abstract_class?
|
221
|
+
|
222
|
+
if self == ActiveRecord::Base
|
223
|
+
descendants.each(&:auto_upgrade!)
|
224
|
+
clear_tables!
|
225
|
+
else
|
226
|
+
# If table doesn't exist, create it
|
227
|
+
unless connection.tables.include?(table_name)
|
228
|
+
# TODO: create_table options
|
229
|
+
class << connection; attr_accessor :table_definition; end unless connection.respond_to?(:table_definition=)
|
230
|
+
connection.table_definition = table_definition
|
231
|
+
connection.create_table(table_name)
|
232
|
+
connection.table_definition = init_table_definition(connection)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Add this to our schema tables
|
236
|
+
schema_tables << table_name unless schema_tables.include?(table_name)
|
237
|
+
|
238
|
+
# Generate fields from associations
|
239
|
+
if reflect_on_all_associations.any?
|
240
|
+
reflect_on_all_associations.each do |association|
|
241
|
+
foreign_key = association.options[:foreign_key] || "#{association.name}_id"
|
242
|
+
type_key = "#{association.name.to_s}_type"
|
243
|
+
case association.macro
|
244
|
+
when :belongs_to
|
245
|
+
field foreign_key, :as => :integer unless fields.key?(foreign_key.to_s)
|
246
|
+
if association.options[:polymorphic]
|
247
|
+
field type_key, :as => :string unless fields.key?(type_key.to_s)
|
248
|
+
index [foreign_key, type_key]
|
249
|
+
else
|
250
|
+
index foreign_key
|
251
|
+
end
|
252
|
+
when :has_and_belongs_to_many
|
253
|
+
table = if name = association.options[:join_table]
|
254
|
+
name.to_s
|
255
|
+
else
|
256
|
+
[table_name, association.name.to_s].sort.join("_")
|
257
|
+
end
|
258
|
+
unless connection.tables.include?(table.to_s)
|
259
|
+
foreign_key = association.options[:foreign_key] || association.foreign_key
|
260
|
+
association_foreign_key = association.options[:association_foreign_key] || association.association_foreign_key
|
261
|
+
connection.create_table(table, :id => false) do |t|
|
262
|
+
t.integer foreign_key
|
263
|
+
t.integer association_foreign_key
|
264
|
+
end
|
265
|
+
index_name = connection.index_name(table, :column => [foreign_key, association_foreign_key])
|
266
|
+
index_name = index_name[0...connection.index_name_length] if index_name.length > connection.index_name_length
|
267
|
+
connection.add_index table, [foreign_key, association_foreign_key], :name => index_name, :unique => true
|
268
|
+
end
|
269
|
+
# Add join table to our schema tables
|
270
|
+
schema_tables << table unless schema_tables.include?(table)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Add to schema inheritance column if necessary
|
276
|
+
if descendants.present?
|
277
|
+
field inheritance_column, :as => :string unless fields.key?(inheritance_column.to_s)
|
278
|
+
index inheritance_column
|
279
|
+
end
|
280
|
+
|
281
|
+
# Remove fields from db no longer in schema
|
282
|
+
(fields_in_db.keys - fields.keys & fields_in_db.keys).each do |field|
|
283
|
+
column = fields_in_db[field]
|
284
|
+
connection.remove_column table_name, column.name
|
285
|
+
end
|
286
|
+
|
287
|
+
# Add fields to db new to schema
|
288
|
+
(fields.keys - fields_in_db.keys).each do |field|
|
289
|
+
column = fields[field]
|
290
|
+
options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
|
291
|
+
options[:default] = column.default unless column.default.nil?
|
292
|
+
options[:null] = column.null unless column.null.nil?
|
293
|
+
connection.add_column table_name, column.name, column.type.to_sym, options
|
294
|
+
end
|
295
|
+
|
296
|
+
# Change attributes of existent columns
|
297
|
+
(fields.keys & fields_in_db.keys).each do |field|
|
298
|
+
if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
|
299
|
+
changed = false # flag
|
300
|
+
new_type = fields[field].type.to_sym
|
301
|
+
new_attr = {}
|
302
|
+
|
303
|
+
# First, check if the field type changed
|
304
|
+
old_sql_type = get_sql_field_type(fields_in_db[field])
|
305
|
+
new_sql_type = get_sql_field_type(fields[field])
|
306
|
+
|
307
|
+
if old_sql_type != new_sql_type
|
308
|
+
logger.debug "[MiniRecord] Detected schema change for #{table_name}.#{field}#type " +
|
309
|
+
" from #{old_sql_type.inspect} to #{new_sql_type.inspect}" if logger
|
310
|
+
changed = true
|
311
|
+
end
|
312
|
+
|
313
|
+
# Special catch for precision/scale, since *both* must be specified together
|
314
|
+
# Always include them in the attr struct, but they'll only get applied if changed = true
|
315
|
+
new_attr[:precision] = fields[field][:precision]
|
316
|
+
new_attr[:scale] = fields[field][:scale]
|
317
|
+
|
318
|
+
# If we have precision this is also the limit
|
319
|
+
fields[field][:limit] ||= fields[field][:precision]
|
320
|
+
|
321
|
+
# Next, iterate through our extended attributes, looking for any differences
|
322
|
+
# This catches stuff like :null, :precision, etc
|
323
|
+
# Ignore junk attributes that different versions of Rails include
|
324
|
+
fields[field].each_pair do |att,value|
|
325
|
+
next unless [:name, :limit, :precision, :scale, :default, :null].include?(att)
|
326
|
+
value = true if att == :null && value.nil?
|
327
|
+
old_value = fields_in_db[field].send(att)
|
328
|
+
if value != old_value
|
329
|
+
logger.debug "[MiniRecord] Detected schema change for #{table_name}.#{field}##{att} " +
|
330
|
+
"from #{old_value.inspect} to #{value.inspect}" if logger
|
331
|
+
new_attr[att] = value
|
332
|
+
changed = true
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Change the column if applicable
|
337
|
+
connection.change_column table_name, field, new_type, new_attr if changed
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
remove_foreign_keys if connection.respond_to?(:foreign_keys)
|
342
|
+
|
343
|
+
# Remove old index
|
344
|
+
(indexes_in_db.keys - indexes.keys).each do |name|
|
345
|
+
connection.remove_index(table_name, :name => name)
|
346
|
+
end
|
347
|
+
|
348
|
+
# Add indexes
|
349
|
+
indexes.each do |name, options|
|
350
|
+
options = options.dup
|
351
|
+
unless connection.indexes(table_name).detect { |i| i.name == name }
|
352
|
+
connection.add_index(table_name, options.delete(:column), options)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
add_foreign_keys if connection.respond_to?(:foreign_keys)
|
357
|
+
|
358
|
+
# Reload column information
|
359
|
+
reset_column_information
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end # ClassMethods
|
363
|
+
end # AutoSchema
|
364
|
+
end # MiniRecord
|