mini_record-cj 0.3.7
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.
- 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
data/mini_record.gemspec
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "mini_record/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "mini_record-cj"
|
|
7
|
+
s.version = MiniRecord::VERSION
|
|
8
|
+
s.authors = ["Davide D'Agostino"]
|
|
9
|
+
s.email = ["d.dagostino@lipsiasoft.com"]
|
|
10
|
+
s.homepage = "https://github.com/DAddYE/mini_record"
|
|
11
|
+
s.summary = %q{MiniRecord is a micro gem that allow you to write schema inside your model as you can do in DataMapper.}
|
|
12
|
+
s.description = %q{
|
|
13
|
+
With it you can add the ability to create columns outside the default schema, directly
|
|
14
|
+
in your model in a similar way that you just know in others projects
|
|
15
|
+
like DataMapper or MongoMapper.
|
|
16
|
+
}.gsub(/^ {4}/, '')
|
|
17
|
+
|
|
18
|
+
s.rubyforge_project = "mini_record"
|
|
19
|
+
|
|
20
|
+
s.files = `git ls-files`.split("\n")
|
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
23
|
+
s.require_paths = ["lib"]
|
|
24
|
+
|
|
25
|
+
# specify any dependencies here; for example:
|
|
26
|
+
# s.add_development_dependency "rspec"
|
|
27
|
+
s.add_dependency "activerecord", ">=3.2.0"
|
|
28
|
+
end
|
data/test/helper.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'rubygems' unless defined?(Gem)
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
require 'mini_record'
|
|
4
|
+
require 'minitest/autorun'
|
|
5
|
+
|
|
6
|
+
class ActiveRecord::Base
|
|
7
|
+
class << self
|
|
8
|
+
attr_accessor :logs
|
|
9
|
+
|
|
10
|
+
def db_fields
|
|
11
|
+
connection.columns(table_name).inject({}) do |hash, column|
|
|
12
|
+
hash[column.name.to_sym] = column
|
|
13
|
+
hash
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def db_columns
|
|
18
|
+
connection.columns(table_name).map(&:name).sort
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def db_indexes
|
|
22
|
+
connection.indexes(table_name).map(&:name).sort
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def schema_columns
|
|
26
|
+
table_definition.columns.map { |c| c.name.to_s }.sort
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def schema_fields
|
|
30
|
+
table_definition.columns.inject({}) do |hash, column|
|
|
31
|
+
hash[column.name.to_sym] = column
|
|
32
|
+
hash
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def queries(pragma=false)
|
|
37
|
+
ActiveRecord::Base.logs.string.gsub(/\e\[[\d;]+m/, '').lines.reject { |l| !pragma && l =~ /pragma/i }.join("\n")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def auto_upgrade!
|
|
41
|
+
ActiveRecord::Base.logs = StringIO.new
|
|
42
|
+
ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(ActiveRecord::Base.logs)
|
|
43
|
+
silence_stream(STDERR) { super }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end # ActiveRecord::Base
|
|
47
|
+
|
|
48
|
+
# Setup Adatper
|
|
49
|
+
case ENV['DB']
|
|
50
|
+
when 'mysql'
|
|
51
|
+
ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'test', :user => 'root')
|
|
52
|
+
when 'mysql2'
|
|
53
|
+
ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'test', :user => 'root')
|
|
54
|
+
Bundler.require(:test) # require 'foreigner'
|
|
55
|
+
Foreigner.load
|
|
56
|
+
when 'pg', 'postgresql'
|
|
57
|
+
ActiveRecord::Base.establish_connection(:adapter => 'postgresql', :database => 'test', :user => 'postgres', :host => 'localhost')
|
|
58
|
+
else
|
|
59
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Some helpers to minitest
|
|
64
|
+
class MiniTest::Spec
|
|
65
|
+
def connection
|
|
66
|
+
ActiveRecord::Base.connection
|
|
67
|
+
end
|
|
68
|
+
alias :conn :connection
|
|
69
|
+
end
|
data/test/models.rb
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class Person < ActiveRecord::Base
|
|
2
|
+
schema do |s|
|
|
3
|
+
s.string :name
|
|
4
|
+
end
|
|
5
|
+
timestamps
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Post < ActiveRecord::Base
|
|
9
|
+
key :title
|
|
10
|
+
key :body
|
|
11
|
+
key :category, :as => :references
|
|
12
|
+
belongs_to :category
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Category < ActiveRecord::Base
|
|
16
|
+
key :title
|
|
17
|
+
has_many :articles
|
|
18
|
+
has_many :posts
|
|
19
|
+
has_many :items
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Animal < ActiveRecord::Base
|
|
23
|
+
key :name, :index => true
|
|
24
|
+
index :id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Pet < ActiveRecord::Base
|
|
28
|
+
key :name, :index => true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Tool < ActiveRecord::Base
|
|
32
|
+
has_and_belongs_to_many :purposes
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Purpose < ActiveRecord::Base
|
|
36
|
+
has_and_belongs_to_many :tools
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Publisher < ActiveRecord::Base
|
|
40
|
+
has_many :articles
|
|
41
|
+
col :name
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Article < ActiveRecord::Base
|
|
45
|
+
key :title
|
|
46
|
+
belongs_to :publisher
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class Attachment < ActiveRecord::Base
|
|
50
|
+
key :name
|
|
51
|
+
belongs_to :attachable, :polymorphic => true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class Account < ActiveRecord::Base
|
|
55
|
+
key :name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Task < ActiveRecord::Base
|
|
59
|
+
belongs_to :author, :class_name => 'Account'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class Activity < ActiveRecord::Base
|
|
63
|
+
belongs_to :author, :class_name => 'Account', :foreign_key => 'custom_id'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class Page < ActiveRecord::Base
|
|
67
|
+
key :title
|
|
68
|
+
has_and_belongs_to_many :photogalleries
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class Photogallery < ActiveRecord::Base
|
|
72
|
+
key :title
|
|
73
|
+
has_and_belongs_to_many :pages
|
|
74
|
+
end
|
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
require File.expand_path('../helper.rb', __FILE__)
|
|
2
|
+
|
|
3
|
+
describe MiniRecord do
|
|
4
|
+
|
|
5
|
+
before do
|
|
6
|
+
ActiveRecord::Base.clear_reloadable_connections!
|
|
7
|
+
ActiveRecord::Base.clear_cache!
|
|
8
|
+
ActiveRecord::Base.clear_active_connections!
|
|
9
|
+
conn.tables.each { |table| silence_stream(STDERR) { conn.execute "DROP TABLE IF EXISTS #{table}" } }
|
|
10
|
+
ActiveRecord::Base.descendants.each { |klass| Object.send(:remove_const, klass.to_s) if Object.const_defined?(klass.to_s) }
|
|
11
|
+
ActiveSupport::DescendantsTracker.direct_descendants(ActiveRecord::Base).clear
|
|
12
|
+
load File.expand_path('../models.rb', __FILE__)
|
|
13
|
+
ActiveRecord::Base.auto_upgrade!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'has #schema inside model' do
|
|
17
|
+
assert_equal 'people', Person.table_name
|
|
18
|
+
assert_equal %w[created_at id name updated_at], Person.db_columns.sort
|
|
19
|
+
assert_equal Person.db_columns, Person.column_names.sort
|
|
20
|
+
assert_equal Person.schema_columns, Person.column_names.sort
|
|
21
|
+
|
|
22
|
+
# Check surname attribute
|
|
23
|
+
person = Person.create(:name => 'foo')
|
|
24
|
+
assert_equal 'foo', person.name
|
|
25
|
+
assert_raises(NoMethodError){ person.surname }
|
|
26
|
+
|
|
27
|
+
# Test the timestamp columns exist
|
|
28
|
+
assert_respond_to person, :created_at
|
|
29
|
+
assert_respond_to person, :updated_at
|
|
30
|
+
|
|
31
|
+
# Add a column without lost data
|
|
32
|
+
Person.class_eval do
|
|
33
|
+
schema do |p|
|
|
34
|
+
p.string :name
|
|
35
|
+
p.string :surname
|
|
36
|
+
end
|
|
37
|
+
timestamps
|
|
38
|
+
end
|
|
39
|
+
Person.auto_upgrade!
|
|
40
|
+
assert_equal 1, Person.count
|
|
41
|
+
|
|
42
|
+
person = Person.last
|
|
43
|
+
assert_equal 'foo', person.name
|
|
44
|
+
assert_nil person.surname
|
|
45
|
+
|
|
46
|
+
person.update_column(:surname, 'bar')
|
|
47
|
+
assert_equal %w[created_at id name surname updated_at], Person.db_columns.sort
|
|
48
|
+
|
|
49
|
+
# Remove a column without lost data
|
|
50
|
+
Person.class_eval do
|
|
51
|
+
schema do |p|
|
|
52
|
+
p.string :name
|
|
53
|
+
end
|
|
54
|
+
timestamps
|
|
55
|
+
end
|
|
56
|
+
Person.auto_upgrade!
|
|
57
|
+
person = Person.last
|
|
58
|
+
assert_equal 'foo', person.name
|
|
59
|
+
assert_raises(NoMethodError) { person.surname }
|
|
60
|
+
assert_equal %w[created_at id name updated_at], Person.db_columns
|
|
61
|
+
assert_equal Person.column_names.sort, Person.db_columns
|
|
62
|
+
assert_equal Person.column_names.sort, Person.schema_columns
|
|
63
|
+
|
|
64
|
+
# Change column without lost data
|
|
65
|
+
Person.class_eval do
|
|
66
|
+
schema do |p|
|
|
67
|
+
p.text :name
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
person = Person.last
|
|
71
|
+
assert_equal 'foo', person.name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'has #key,col,property,attribute inside model' do
|
|
75
|
+
assert_equal Post.column_names.sort, Post.db_columns
|
|
76
|
+
assert_equal Category.column_names.sort, Category.schema_columns
|
|
77
|
+
|
|
78
|
+
# Check default properties
|
|
79
|
+
category = Category.create(:title => 'category')
|
|
80
|
+
post = Post.create(:title => 'foo', :body => 'bar', :category_id => category.id)
|
|
81
|
+
post = Post.first
|
|
82
|
+
assert_equal 'foo', post.title
|
|
83
|
+
assert_equal 'bar', post.body
|
|
84
|
+
assert_equal category, post.category
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Remove a column
|
|
88
|
+
Post.reset_table_definition!
|
|
89
|
+
Post.class_eval do
|
|
90
|
+
col :name
|
|
91
|
+
col :category, :as => :references
|
|
92
|
+
end
|
|
93
|
+
Post.auto_upgrade!
|
|
94
|
+
refute_includes %w[title body], Post.db_columns
|
|
95
|
+
|
|
96
|
+
post = Post.first
|
|
97
|
+
assert_nil post.name
|
|
98
|
+
assert_equal category, post.category
|
|
99
|
+
assert_raises(NoMethodError, ActiveModel::MissingAttributeError) { post.title }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'has indexes inside model' do
|
|
103
|
+
# Check indexes
|
|
104
|
+
assert Animal.db_indexes.size > 0
|
|
105
|
+
assert_equal Animal.db_indexes, Animal.indexes.keys.sort
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Remove an index
|
|
109
|
+
indexes_was = Animal.db_indexes
|
|
110
|
+
Animal.indexes.delete(indexes_was.pop)
|
|
111
|
+
Animal.auto_upgrade!
|
|
112
|
+
assert_equal indexes_was, Animal.indexes.keys
|
|
113
|
+
assert_equal indexes_was, Animal.db_indexes
|
|
114
|
+
|
|
115
|
+
# Add a new index
|
|
116
|
+
Animal.class_eval do
|
|
117
|
+
col :category, :as => :references, :index => true
|
|
118
|
+
end
|
|
119
|
+
Animal.auto_upgrade!
|
|
120
|
+
new_indexes = indexes_was + %w[index_animals_on_category_id]
|
|
121
|
+
assert_includes Animal.db_columns, 'category_id'
|
|
122
|
+
assert_equal new_indexes.sort, Animal.db_indexes
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'not add already defined indexes' do
|
|
126
|
+
class Foo < ActiveRecord::Base
|
|
127
|
+
index :customer_id, :unique => true, :name => 'by_customer'
|
|
128
|
+
belongs_to :customer
|
|
129
|
+
end
|
|
130
|
+
Foo.auto_upgrade!
|
|
131
|
+
assert_equal 1, Foo.db_indexes.size
|
|
132
|
+
assert_includes Foo.db_indexes, 'by_customer'
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'works with STI' do
|
|
136
|
+
class Dog < Pet; end
|
|
137
|
+
class Cat < Pet; end
|
|
138
|
+
class Kitten < Cat; end
|
|
139
|
+
ActiveRecord::Base.auto_upgrade!
|
|
140
|
+
|
|
141
|
+
# Check inheritance column
|
|
142
|
+
assert_includes Pet.db_columns, "type"
|
|
143
|
+
|
|
144
|
+
# Now, let's we know if STI is working
|
|
145
|
+
Pet.create(:name => "foo")
|
|
146
|
+
Dog.create(:name => "bar")
|
|
147
|
+
Kitten.create(:name => 'foxy')
|
|
148
|
+
assert_equal 1, Dog.count
|
|
149
|
+
assert_equal 'bar', Dog.first.name
|
|
150
|
+
assert_equal 3, Pet.count
|
|
151
|
+
assert_equal %w[foo bar foxy], Pet.all.map(&:name)
|
|
152
|
+
assert_equal 'bar', Dog.first.name
|
|
153
|
+
|
|
154
|
+
# What's happen if we change schema?
|
|
155
|
+
assert_equal Dog.table_definition, Pet.table_definition
|
|
156
|
+
assert_equal Dog.indexes, Pet.indexes
|
|
157
|
+
|
|
158
|
+
Dog.class_eval do
|
|
159
|
+
col :bau
|
|
160
|
+
end
|
|
161
|
+
ActiveRecord::Base.auto_upgrade!
|
|
162
|
+
assert_includes Dog.schema_columns, 'bau'
|
|
163
|
+
assert_includes Pet.db_columns, 'bau'
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'works with custom inheritance column' do
|
|
167
|
+
class User < ActiveRecord::Base
|
|
168
|
+
col :name
|
|
169
|
+
col :surname
|
|
170
|
+
col :role
|
|
171
|
+
def self.inheritance_column; 'role'; end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
class Administrator < User; end
|
|
175
|
+
class Customer < User; end
|
|
176
|
+
|
|
177
|
+
User.auto_upgrade!
|
|
178
|
+
assert_equal 'role', User.inheritance_column
|
|
179
|
+
|
|
180
|
+
Administrator.create(:name => "Davide", :surname => 'DAddYE')
|
|
181
|
+
Customer.create(:name => "Foo", :surname => "Bar")
|
|
182
|
+
assert_equal 1, Administrator.count
|
|
183
|
+
assert_equal 'Davide', Administrator.first.name
|
|
184
|
+
assert_equal 1, Customer.count
|
|
185
|
+
assert_equal 'Foo', Customer.first.name
|
|
186
|
+
assert_equal 2, User.count
|
|
187
|
+
assert_equal 'Administrator', User.first.role
|
|
188
|
+
assert_equal 'Customer', User.last.role
|
|
189
|
+
assert_includes User.db_indexes, 'index_users_on_role'
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it 'allow multiple columns definitions' do
|
|
193
|
+
class Fake < ActiveRecord::Base
|
|
194
|
+
col :name, :surname
|
|
195
|
+
col :category, :group, :as => :references
|
|
196
|
+
end
|
|
197
|
+
Fake.auto_upgrade!
|
|
198
|
+
Fake.create(:name => 'foo', :surname => 'bar', :category_id => 1, :group_id => 2)
|
|
199
|
+
fake = Fake.first
|
|
200
|
+
assert_equal 'foo', fake.name
|
|
201
|
+
assert_equal 'bar', fake.surname
|
|
202
|
+
assert_equal 1, fake.category_id
|
|
203
|
+
assert_equal 2, fake.group_id
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it 'allow custom query' do
|
|
207
|
+
skip unless conn.adapter_name =~ /mysql/i
|
|
208
|
+
|
|
209
|
+
class Foo < ActiveRecord::Base
|
|
210
|
+
col :name, :as => "ENUM('foo','bar')"
|
|
211
|
+
end
|
|
212
|
+
Foo.auto_upgrade!
|
|
213
|
+
assert_match /ENUM/, Foo.queries
|
|
214
|
+
|
|
215
|
+
Foo.auto_upgrade!
|
|
216
|
+
assert_empty Foo.queries
|
|
217
|
+
assert_equal %w[id name], Foo.db_columns
|
|
218
|
+
assert_equal %w[id name], Foo.schema_columns
|
|
219
|
+
|
|
220
|
+
foo = Foo.create(:name => 'test')
|
|
221
|
+
assert_empty Foo.first.name
|
|
222
|
+
|
|
223
|
+
foo.update_column(:name, 'foo')
|
|
224
|
+
|
|
225
|
+
assert_equal 'foo', Foo.first.name
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
describe 'relation #belongs_to' do
|
|
229
|
+
|
|
230
|
+
it 'creates a column and index based on relation' do
|
|
231
|
+
Article.create(:title => 'Hello', :publisher_id => 1)
|
|
232
|
+
Article.first.tap do |a|
|
|
233
|
+
assert_equal 'Hello', a.title
|
|
234
|
+
assert_equal 1, a.publisher_id
|
|
235
|
+
end
|
|
236
|
+
assert_includes Article.db_indexes, 'index_articles_on_publisher_id'
|
|
237
|
+
|
|
238
|
+
# Ensure that associated field/index is not deleted on upgrade
|
|
239
|
+
Article.auto_upgrade!
|
|
240
|
+
assert_equal 1, Article.first.publisher_id
|
|
241
|
+
assert_includes Article.db_indexes, 'index_articles_on_publisher_id'
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'removes a column and index when relation is removed' do
|
|
245
|
+
class Foo < ActiveRecord::Base
|
|
246
|
+
key :name
|
|
247
|
+
belongs_to :image, :polymorphic => true
|
|
248
|
+
end
|
|
249
|
+
Foo.auto_upgrade!
|
|
250
|
+
assert_includes Foo.db_columns, 'name'
|
|
251
|
+
assert_includes Foo.db_columns, 'image_type'
|
|
252
|
+
assert_includes Foo.db_columns, 'image_id'
|
|
253
|
+
assert_includes Foo.db_indexes, 'index_foos_on_image_id_and_image_type'
|
|
254
|
+
|
|
255
|
+
Foo.class_eval do
|
|
256
|
+
reset_table_definition!
|
|
257
|
+
reflections.clear
|
|
258
|
+
indexes.clear
|
|
259
|
+
key :name
|
|
260
|
+
end
|
|
261
|
+
Foo.auto_upgrade!
|
|
262
|
+
assert_includes Foo.db_columns, 'name'
|
|
263
|
+
refute_includes Foo.db_columns, 'image_type'
|
|
264
|
+
refute_includes Foo.db_columns, 'image_id'
|
|
265
|
+
assert_empty Foo.db_indexes
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'creates columns and index based on polymorphic relation' do
|
|
269
|
+
Attachment.create(:name => 'Avatar', :attachable_id => 1, :attachable_type => 'Post')
|
|
270
|
+
Attachment.first.tap do |attachment|
|
|
271
|
+
assert_equal 'Avatar', attachment.name
|
|
272
|
+
assert_equal 1, attachment.attachable_id
|
|
273
|
+
assert_equal 'Post', attachment.attachable_type
|
|
274
|
+
end
|
|
275
|
+
index = 'index_attachments_on_attachable_id_and_attachable_type'
|
|
276
|
+
assert_includes Attachment.db_indexes, index
|
|
277
|
+
|
|
278
|
+
# Ensure that associated fields/indexes are not deleted on subsequent upgrade
|
|
279
|
+
Attachment.auto_upgrade!
|
|
280
|
+
assert_equal 1, Attachment.first.attachable_id
|
|
281
|
+
assert_equal 'Post', Attachment.first.attachable_type
|
|
282
|
+
assert_includes Attachment.db_indexes, index
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it 'should support :class_name' do
|
|
286
|
+
assert_includes Task.schema_columns, 'author_id'
|
|
287
|
+
assert_includes Task.db_columns, 'author_id'
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it 'should support :foreign_key' do
|
|
291
|
+
assert_includes Activity.schema_columns, 'custom_id'
|
|
292
|
+
assert_includes Activity.db_columns, 'custom_id'
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it 'should memonize in schema relationships' do
|
|
296
|
+
silence_stream(STDERR) { conn.create_table('foos') }
|
|
297
|
+
conn.add_column :foos, :name, :string
|
|
298
|
+
conn.add_column :foos, :bar_id, :integer
|
|
299
|
+
conn.add_index :foos, :bar_id
|
|
300
|
+
class Foo < ActiveRecord::Base
|
|
301
|
+
col :name
|
|
302
|
+
belongs_to :bar
|
|
303
|
+
end
|
|
304
|
+
assert_includes Foo.db_columns, 'name'
|
|
305
|
+
assert_includes Foo.db_columns, 'bar_id'
|
|
306
|
+
assert_includes Foo.db_indexes, 'index_foos_on_bar_id'
|
|
307
|
+
|
|
308
|
+
Foo.auto_upgrade!
|
|
309
|
+
assert_includes Foo.schema_columns, 'name'
|
|
310
|
+
assert_includes Foo.schema_columns, 'bar_id'
|
|
311
|
+
assert_includes Foo.indexes, 'index_foos_on_bar_id'
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
it 'should add new columns without lost belongs_to schema' do
|
|
315
|
+
publisher = Publisher.create(:name => 'foo')
|
|
316
|
+
article = Article.create(:title => 'bar', :publisher => publisher)
|
|
317
|
+
assert article.valid?
|
|
318
|
+
assert_includes Article.indexes, 'index_articles_on_publisher_id'
|
|
319
|
+
|
|
320
|
+
# Here we perform a schema change
|
|
321
|
+
Article.key :body
|
|
322
|
+
Article.auto_upgrade!
|
|
323
|
+
article.reload
|
|
324
|
+
assert_nil article.body
|
|
325
|
+
|
|
326
|
+
article.update_column(:body, 'null')
|
|
327
|
+
assert_equal 'null', article.body
|
|
328
|
+
|
|
329
|
+
# Finally check the index existance
|
|
330
|
+
assert_includes Article.db_indexes, 'index_articles_on_publisher_id'
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'should not override previous defined column relation' do
|
|
334
|
+
class Foo < ActiveRecord::Base
|
|
335
|
+
key :user, :as => :references, :null => false, :limit => 4
|
|
336
|
+
belongs_to :user
|
|
337
|
+
end
|
|
338
|
+
Foo.auto_upgrade!
|
|
339
|
+
assert_equal 4, Foo.db_fields[:user_id].limit
|
|
340
|
+
assert_equal false, Foo.db_fields[:user_id].null
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it 'add/remove foreign key with :foreign option, when Foreigner gem used on mysql' do
|
|
344
|
+
skip unless conn.adapter_name =~ /mysql/i
|
|
345
|
+
|
|
346
|
+
class Book < ActiveRecord::Base
|
|
347
|
+
belongs_to :publisher
|
|
348
|
+
index :publisher_id, :foreign => true
|
|
349
|
+
end
|
|
350
|
+
Book.auto_upgrade!
|
|
351
|
+
|
|
352
|
+
assert_includes Book.db_columns, 'publisher_id'
|
|
353
|
+
assert_includes Book.db_indexes, 'index_books_on_publisher_id'
|
|
354
|
+
|
|
355
|
+
assert connection.foreign_keys(:books).detect {|fk| fk.options[:column] == 'publisher_id'}
|
|
356
|
+
|
|
357
|
+
Object.send(:remove_const, :Book)
|
|
358
|
+
class Book < ActiveRecord::Base
|
|
359
|
+
belongs_to :publisher
|
|
360
|
+
index :publisher_id, :foreign => false
|
|
361
|
+
end
|
|
362
|
+
Book.auto_upgrade!
|
|
363
|
+
|
|
364
|
+
assert_nil connection.foreign_keys(:books).detect {|fk| fk.options[:column] == 'publisher_id'}
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
it 'add/remove named foreign key with :foreign option, when Foreigner gem used on mysql' do
|
|
368
|
+
skip unless conn.adapter_name =~ /mysql/i
|
|
369
|
+
|
|
370
|
+
class Book < ActiveRecord::Base
|
|
371
|
+
belongs_to :publisher
|
|
372
|
+
index :publisher_id, :name => 'my_super_publisher_id_fk', :foreign => true
|
|
373
|
+
end
|
|
374
|
+
Book.auto_upgrade!
|
|
375
|
+
|
|
376
|
+
assert_includes Book.db_columns, 'publisher_id'
|
|
377
|
+
assert_includes Book.db_indexes, 'my_super_publisher_id_fk'
|
|
378
|
+
|
|
379
|
+
assert connection.foreign_keys(:books).detect {|fk| fk.options[:column] == 'publisher_id'}
|
|
380
|
+
|
|
381
|
+
Object.send(:remove_const, :Book)
|
|
382
|
+
class Book < ActiveRecord::Base
|
|
383
|
+
belongs_to :publisher
|
|
384
|
+
index :publisher_id, :name => 'my_super_publisher_id_fk', :foreign => false
|
|
385
|
+
end
|
|
386
|
+
Book.auto_upgrade!
|
|
387
|
+
|
|
388
|
+
assert_nil connection.foreign_keys(:books).detect {|fk| fk.options[:column] == 'publisher_id'}
|
|
389
|
+
Object.send(:remove_const, :Book)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
it 'support :foreign option in the index with custom :foreign_key in the belong_to association' do
|
|
393
|
+
skip unless conn.adapter_name =~ /mysql/i
|
|
394
|
+
|
|
395
|
+
class Book < ActiveRecord::Base
|
|
396
|
+
belongs_to :second_publisher, :foreign_key => 'second_publisher_id', :class_name => 'Publisher'
|
|
397
|
+
index :second_publisher_id, :foreign => true
|
|
398
|
+
end
|
|
399
|
+
Book.auto_upgrade!
|
|
400
|
+
|
|
401
|
+
assert_includes Book.db_columns, 'second_publisher_id'
|
|
402
|
+
assert_includes Book.db_indexes, 'index_books_on_second_publisher_id'
|
|
403
|
+
|
|
404
|
+
assert connection.foreign_keys(:books).detect {|fk| fk.options[:column] == 'second_publisher_id'}
|
|
405
|
+
|
|
406
|
+
Object.send(:remove_const, :Book)
|
|
407
|
+
class Book < ActiveRecord::Base
|
|
408
|
+
belongs_to :second_publisher, :foreign_key => 'second_publisher_id', :class_name => 'Publisher'
|
|
409
|
+
index :second_publisher_id, :foreign => false
|
|
410
|
+
end
|
|
411
|
+
Book.auto_upgrade!
|
|
412
|
+
|
|
413
|
+
assert_nil connection.foreign_keys(:books).detect {|fk| fk.options[:column] == 'second_publisher_id'}
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
describe 'relation #habtm' do
|
|
419
|
+
it 'creates a join table with indexes for has_and_belongs_to_many relations' do
|
|
420
|
+
tables = Tool.connection.tables
|
|
421
|
+
assert_includes tables, 'purposes_tools'
|
|
422
|
+
|
|
423
|
+
index = 'index_purposes_tools_on_tool_id_and_purpose_id'
|
|
424
|
+
assert_includes Tool.connection.indexes('purposes_tools').map(&:name), index
|
|
425
|
+
|
|
426
|
+
# Ensure that join table is not deleted on subsequent upgrade
|
|
427
|
+
Tool.auto_upgrade!
|
|
428
|
+
assert_includes tables, 'purposes_tools'
|
|
429
|
+
assert_includes Tool.connection.indexes('purposes_tools').map(&:name), index
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
it 'drops join table if has_and_belongs_to_many relation is deleted' do
|
|
433
|
+
Tool.schema_tables.delete('purposes_tools')
|
|
434
|
+
refute_includes ActiveRecord::Base.schema_tables, 'purposes_tools'
|
|
435
|
+
|
|
436
|
+
ActiveRecord::Base.clear_tables!
|
|
437
|
+
refute_includes Tool.connection.tables, 'purposes_tools'
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
it 'has_and_belongs_to_many with custom join_table and foreign keys' do
|
|
441
|
+
class Foo < ActiveRecord::Base
|
|
442
|
+
has_and_belongs_to_many :watchers, :join_table => :watching, :foreign_key => :custom_foo_id, :association_foreign_key => :customer_id
|
|
443
|
+
end
|
|
444
|
+
Foo.auto_upgrade!
|
|
445
|
+
assert_includes conn.tables, 'watching'
|
|
446
|
+
|
|
447
|
+
cols = conn.columns('watching').map(&:name)
|
|
448
|
+
refute_includes cols, 'id'
|
|
449
|
+
assert_includes cols, 'custom_foo_id'
|
|
450
|
+
assert_includes cols, 'customer_id'
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
it 'creates a join table with indexes with long indexes names' do
|
|
454
|
+
class Foo < ActiveRecord::Base
|
|
455
|
+
has_and_belongs_to_many :people, :join_table => :long_people,
|
|
456
|
+
:foreign_key => :custom_long_long_long_long_id,
|
|
457
|
+
:association_foreign_key => :customer_super_long_very_long_trust_me_id
|
|
458
|
+
end
|
|
459
|
+
Foo.auto_upgrade!
|
|
460
|
+
index_name = 'index_long_people_on_custom_long_long_long_long_id_and_customer_super_long_very_long_trust_me_id'[0...conn.index_name_length]
|
|
461
|
+
assert_includes conn.tables, 'people'
|
|
462
|
+
assert_includes conn.indexes(:long_people).map(&:name), index_name
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
it 'adds unique index' do
|
|
466
|
+
page = Page.create(:title => 'Foo')
|
|
467
|
+
photogallery = Photogallery.create(:title => 'Bar')
|
|
468
|
+
assert photogallery.valid?
|
|
469
|
+
|
|
470
|
+
photogallery.pages << page
|
|
471
|
+
refute_empty Photogallery.queries
|
|
472
|
+
assert_includes photogallery.reload.pages, page
|
|
473
|
+
assert_raises(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid){ photogallery.pages << page }
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
it 'should add multiple index' do
|
|
478
|
+
class Foo < ActiveRecord::Base
|
|
479
|
+
key :name, :surname, :index => true
|
|
480
|
+
end
|
|
481
|
+
Foo.auto_upgrade!
|
|
482
|
+
assert_includes Foo.db_indexes, 'index_foos_on_name'
|
|
483
|
+
assert_includes Foo.db_indexes, 'index_foos_on_surname'
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
it 'should create a unique index' do
|
|
487
|
+
class Foo < ActiveRecord::Base
|
|
488
|
+
key :name, :surname
|
|
489
|
+
add_index([:name, :surname], :unique => true)
|
|
490
|
+
end
|
|
491
|
+
Foo.auto_upgrade!
|
|
492
|
+
db_indexes = Foo.connection.indexes('foos')[0]
|
|
493
|
+
assert_equal 'index_foos_on_name_and_surname', db_indexes.name
|
|
494
|
+
assert db_indexes.unique
|
|
495
|
+
assert_equal %w[name surname], db_indexes.columns.sort
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
it 'should change #limit' do
|
|
499
|
+
class Foo < ActiveRecord::Base
|
|
500
|
+
key :number, :as => :integer
|
|
501
|
+
key :string, :limit => 100
|
|
502
|
+
end
|
|
503
|
+
Foo.auto_upgrade!
|
|
504
|
+
assert_match /CREATE TABLE/, Foo.queries
|
|
505
|
+
|
|
506
|
+
Foo.auto_upgrade!
|
|
507
|
+
refute_match /alter/i, Foo.queries
|
|
508
|
+
|
|
509
|
+
# According to this:
|
|
510
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L476-487
|
|
511
|
+
Foo.key :number, :as => :integer, :limit => 4
|
|
512
|
+
Foo.auto_upgrade!
|
|
513
|
+
case conn.adapter_name
|
|
514
|
+
when /sqlite/i
|
|
515
|
+
# In sqlite there is a difference between limit: 4 and limit: 11
|
|
516
|
+
assert_match 'altered', Foo.queries
|
|
517
|
+
assert_equal 4, Foo.db_fields[:number].limit
|
|
518
|
+
assert_equal 4, Foo.schema_fields[:number].limit
|
|
519
|
+
when /mysql/i
|
|
520
|
+
# In mysql according to this: http://goo.gl/bjZE7 limit: 4 is same of limit:11
|
|
521
|
+
assert_empty Foo.queries
|
|
522
|
+
assert_equal 4, Foo.db_fields[:number].limit
|
|
523
|
+
assert_equal 4, Foo.schema_fields[:number].limit
|
|
524
|
+
when /postgres/i
|
|
525
|
+
# In postgres limit: 4 will be translated to nil
|
|
526
|
+
assert_match /ALTER COLUMN "number" TYPE integer$/, Foo.queries
|
|
527
|
+
assert_equal nil, Foo.db_fields[:number].limit
|
|
528
|
+
assert_equal 4, Foo.schema_fields[:number].limit
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# Change limit to string
|
|
532
|
+
Foo.key :string, :limit => 255
|
|
533
|
+
Foo.auto_upgrade!
|
|
534
|
+
refute_empty Foo.queries
|
|
535
|
+
assert_equal 255, Foo.db_fields[:string].limit
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
it 'should change #null' do
|
|
539
|
+
class Foo < ActiveRecord::Base
|
|
540
|
+
key :string
|
|
541
|
+
end
|
|
542
|
+
Foo.auto_upgrade!
|
|
543
|
+
assert Foo.db_fields[:string].null
|
|
544
|
+
|
|
545
|
+
# Same as above
|
|
546
|
+
Foo.key :string, :null => true
|
|
547
|
+
Foo.auto_upgrade!
|
|
548
|
+
refute_match /alter/i, Foo.queries
|
|
549
|
+
assert Foo.db_fields[:string].null
|
|
550
|
+
|
|
551
|
+
Foo.key :string, :null => nil
|
|
552
|
+
Foo.auto_upgrade!
|
|
553
|
+
refute_match /alter/i, Foo.queries
|
|
554
|
+
assert Foo.db_fields[:string].null
|
|
555
|
+
|
|
556
|
+
Foo.key :string, :null => false
|
|
557
|
+
Foo.auto_upgrade!
|
|
558
|
+
assert_match /alter/i, Foo.queries
|
|
559
|
+
refute Foo.db_fields[:string].null
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
it 'should change #scale #precision' do
|
|
563
|
+
class Foo < ActiveRecord::Base
|
|
564
|
+
field :currency, :as => :decimal, :precision => 8, :scale => 2
|
|
565
|
+
end
|
|
566
|
+
Foo.auto_upgrade!
|
|
567
|
+
assert_equal 8, Foo.db_fields[:currency].precision
|
|
568
|
+
assert_equal 2, Foo.db_fields[:currency].scale
|
|
569
|
+
assert_equal 8, Foo.db_fields[:currency].limit
|
|
570
|
+
|
|
571
|
+
Foo.auto_upgrade!
|
|
572
|
+
refute_match /alter/i, Foo.queries
|
|
573
|
+
|
|
574
|
+
Foo.field :currency, :as => :decimal, :precision => 4, :scale => 2, :limit => 5
|
|
575
|
+
Foo.auto_upgrade!
|
|
576
|
+
assert_match /alter/i, Foo.queries
|
|
577
|
+
assert_equal 4, Foo.db_fields[:currency].precision
|
|
578
|
+
assert_equal 2, Foo.db_fields[:currency].scale
|
|
579
|
+
assert_equal 4, Foo.db_fields[:currency].limit
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
it 'should ignore abstract classes' do
|
|
583
|
+
class Foo < ActiveRecord::Base
|
|
584
|
+
self.abstract_class = true
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
class Bar < Foo
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
Foo.auto_upgrade!
|
|
591
|
+
Bar.auto_upgrade!
|
|
592
|
+
|
|
593
|
+
tables = Foo.connection.tables
|
|
594
|
+
|
|
595
|
+
refute_includes tables, 'foos'
|
|
596
|
+
refute_includes tables, ''
|
|
597
|
+
assert_includes tables, 'bars'
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
it 'should prevent abstract table class to leak columns to other tables' do
|
|
601
|
+
|
|
602
|
+
class Base < ActiveRecord::Base
|
|
603
|
+
self.abstract_class = true
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
class User < Base
|
|
607
|
+
col :name
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
class Book < Base
|
|
611
|
+
col :title
|
|
612
|
+
col :author
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
User.auto_upgrade!
|
|
616
|
+
Book.auto_upgrade!
|
|
617
|
+
|
|
618
|
+
assert_equal ['id', 'name'], User.db_columns.sort
|
|
619
|
+
assert_equal ['author', 'id', 'title'], Book.db_columns.sort
|
|
620
|
+
end
|
|
621
|
+
end
|