mini_record 0.3.0 → 0.3.1
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/.travis.yml +2 -1
- data/Gemfile +7 -3
- data/Rakefile +2 -2
- data/lib/mini_record/auto_schema.rb +87 -62
- data/lib/mini_record/version.rb +1 -1
- data/mini_record.gemspec +1 -1
- data/test/helper.rb +65 -0
- data/{spec → test}/models.rb +10 -3
- data/test/test_mini_record.rb +503 -0
- metadata +11 -11
- data/spec/mini_record_spec.rb +0 -296
- data/spec/spec_helper.rb +0 -26
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
|
@@ -14,10 +14,10 @@ module MiniRecord
|
|
|
14
14
|
return superclass.table_definition unless superclass == ActiveRecord::Base
|
|
15
15
|
|
|
16
16
|
@_table_definition ||= begin
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
tb = ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
|
|
18
|
+
tb.primary_key(primary_key)
|
|
19
|
+
tb
|
|
20
|
+
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def indexes
|
|
@@ -26,31 +26,66 @@ module MiniRecord
|
|
|
26
26
|
@_indexes ||= {}
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def indexes_in_db
|
|
30
|
+
connection.indexes(table_name).inject({}) do |hash, index|
|
|
31
|
+
hash[index.name] = index
|
|
32
|
+
hash
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def fields
|
|
37
|
+
table_definition.columns.inject({}) do |hash, column|
|
|
38
|
+
hash[column.name] = column
|
|
39
|
+
hash
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fields_in_db
|
|
44
|
+
connection.columns(table_name).inject({}) do |hash, column|
|
|
45
|
+
hash[column.name] = column
|
|
46
|
+
hash
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def field(*args)
|
|
30
51
|
return unless connection?
|
|
31
52
|
|
|
32
|
-
options
|
|
33
|
-
type
|
|
53
|
+
options = args.extract_options!
|
|
54
|
+
type = options.delete(:as) || options.delete(:type) || :string
|
|
55
|
+
index = options.delete(:index)
|
|
56
|
+
|
|
34
57
|
args.each do |column_name|
|
|
35
|
-
|
|
58
|
+
|
|
59
|
+
# Allow custom types like:
|
|
60
|
+
# t.column :type, "ENUM('EMPLOYEE','CLIENT','SUPERUSER','DEVELOPER')"
|
|
61
|
+
if type.is_a?(String)
|
|
62
|
+
# will be converted in: t.column :type, "ENUM('EMPLOYEE','CLIENT')"
|
|
63
|
+
table_definition.column(column_name, type, options.reverse_merge(:limit => 0))
|
|
64
|
+
else
|
|
65
|
+
# wil be converted in: t.string :name
|
|
66
|
+
table_definition.send(type, column_name, options)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get the correct column_name i.e. in field :category, :as => :references
|
|
36
70
|
column_name = table_definition.columns[-1].name
|
|
37
|
-
|
|
71
|
+
|
|
72
|
+
# Parse indexes
|
|
73
|
+
case index
|
|
38
74
|
when Hash
|
|
39
|
-
add_index(options.delete(:column) || column_name,
|
|
75
|
+
add_index(options.delete(:column) || column_name, index)
|
|
40
76
|
when TrueClass
|
|
41
77
|
add_index(column_name)
|
|
42
78
|
when String, Symbol, Array
|
|
43
|
-
add_index(
|
|
79
|
+
add_index(index)
|
|
44
80
|
end
|
|
45
81
|
end
|
|
46
82
|
end
|
|
47
|
-
alias :key
|
|
48
|
-
alias :property
|
|
49
|
-
alias :field
|
|
50
|
-
alias :attribute :col
|
|
83
|
+
alias :key :field
|
|
84
|
+
alias :property :field
|
|
85
|
+
alias :col :field
|
|
51
86
|
|
|
52
87
|
def timestamps
|
|
53
|
-
|
|
88
|
+
field :created_at, :updated_at, :as => :datetime
|
|
54
89
|
end
|
|
55
90
|
|
|
56
91
|
def reset_table_definition!
|
|
@@ -63,14 +98,10 @@ module MiniRecord
|
|
|
63
98
|
yield table_definition
|
|
64
99
|
table_definition
|
|
65
100
|
end
|
|
66
|
-
alias :keys :schema
|
|
67
|
-
alias :properties :schema
|
|
68
|
-
alias :fields :schema
|
|
69
|
-
alias :attributes :schema
|
|
70
101
|
|
|
71
102
|
def add_index(column_name, options={})
|
|
72
103
|
index_name = connection.index_name(table_name, :column => column_name)
|
|
73
|
-
indexes[index_name] = options.merge(:column => column_name)
|
|
104
|
+
indexes[index_name] = options.merge(:column => column_name) unless indexes.key?(index_name)
|
|
74
105
|
index_name
|
|
75
106
|
end
|
|
76
107
|
alias :index :add_index
|
|
@@ -83,7 +114,6 @@ module MiniRecord
|
|
|
83
114
|
end
|
|
84
115
|
|
|
85
116
|
def clear_tables!
|
|
86
|
-
# Drop unsued tables
|
|
87
117
|
(connection.tables - schema_tables).each do |name|
|
|
88
118
|
connection.drop_table(name)
|
|
89
119
|
schema_tables.delete(name)
|
|
@@ -97,7 +127,7 @@ module MiniRecord
|
|
|
97
127
|
descendants.each(&:auto_upgrade!)
|
|
98
128
|
clear_tables!
|
|
99
129
|
else
|
|
100
|
-
#
|
|
130
|
+
# If table doesn't exist, create it
|
|
101
131
|
unless connection.tables.include?(table_name)
|
|
102
132
|
# TODO: create_table options
|
|
103
133
|
class << connection; attr_accessor :table_definition; end unless connection.respond_to?(:table_definition=)
|
|
@@ -109,25 +139,19 @@ module MiniRecord
|
|
|
109
139
|
# Add this to our schema tables
|
|
110
140
|
schema_tables << table_name unless schema_tables.include?(table_name)
|
|
111
141
|
|
|
112
|
-
# Grab database columns
|
|
113
|
-
fields_in_db = connection.columns(table_name).inject({}) do |hash, column|
|
|
114
|
-
hash[column.name] = column
|
|
115
|
-
hash
|
|
116
|
-
end
|
|
117
|
-
|
|
118
142
|
# Generate fields from associations
|
|
119
143
|
if reflect_on_all_associations.any?
|
|
120
144
|
reflect_on_all_associations.each do |association|
|
|
121
|
-
foreign_key = association.options[:foreign_key] || "#{association.name
|
|
145
|
+
foreign_key = association.options[:foreign_key] || "#{association.name}_id"
|
|
122
146
|
type_key = "#{association.name.to_s}_type"
|
|
123
147
|
case association.macro
|
|
124
148
|
when :belongs_to
|
|
125
|
-
|
|
149
|
+
field foreign_key, :as => :integer unless fields.key?(foreign_key.to_s)
|
|
126
150
|
if association.options[:polymorphic]
|
|
127
|
-
|
|
128
|
-
|
|
151
|
+
field type_key, :as => :string unless fields.key?(type_key.to_s)
|
|
152
|
+
index [foreign_key, type_key]
|
|
129
153
|
else
|
|
130
|
-
|
|
154
|
+
index foreign_key
|
|
131
155
|
end
|
|
132
156
|
when :has_and_belongs_to_many
|
|
133
157
|
table = if name = association.options[:join_table]
|
|
@@ -135,15 +159,16 @@ module MiniRecord
|
|
|
135
159
|
else
|
|
136
160
|
[table_name, association.name.to_s].sort.join("_")
|
|
137
161
|
end
|
|
138
|
-
index = ""
|
|
139
162
|
unless connection.tables.include?(table.to_s)
|
|
140
|
-
foreign_key = association.options[:foreign_key] ||
|
|
141
|
-
association_foreign_key = association.options[:association_foreign_key] ||
|
|
163
|
+
foreign_key = association.options[:foreign_key] || association.foreign_key
|
|
164
|
+
association_foreign_key = association.options[:association_foreign_key] || association.association_foreign_key
|
|
142
165
|
connection.create_table(table, :id => false) do |t|
|
|
143
166
|
t.integer foreign_key
|
|
144
167
|
t.integer association_foreign_key
|
|
145
168
|
end
|
|
146
|
-
connection.
|
|
169
|
+
index_name = connection.index_name(table, :column => [foreign_key, association_foreign_key])
|
|
170
|
+
index_name = index_name[0...connection.index_name_length] if index_name.length > connection.index_name_length
|
|
171
|
+
connection.add_index table, [foreign_key, association_foreign_key], :name => index_name, :unique => true
|
|
147
172
|
end
|
|
148
173
|
# Add join table to our schema tables
|
|
149
174
|
schema_tables << table unless schema_tables.include?(table)
|
|
@@ -151,54 +176,57 @@ module MiniRecord
|
|
|
151
176
|
end
|
|
152
177
|
end
|
|
153
178
|
|
|
154
|
-
# Grab new schema
|
|
155
|
-
fields_in_schema = table_definition.columns.inject({}) do |hash, column|
|
|
156
|
-
hash[column.name.to_s] = column
|
|
157
|
-
hash
|
|
158
|
-
end
|
|
159
|
-
|
|
160
179
|
# Add to schema inheritance column if necessary
|
|
161
|
-
if descendants.present?
|
|
162
|
-
|
|
180
|
+
if descendants.present?
|
|
181
|
+
field inheritance_column, :as => :string unless fields.key?(inheritance_column.to_s)
|
|
182
|
+
index inheritance_column
|
|
163
183
|
end
|
|
164
184
|
|
|
165
185
|
# Remove fields from db no longer in schema
|
|
166
|
-
(fields_in_db.keys -
|
|
186
|
+
(fields_in_db.keys - fields.keys & fields_in_db.keys).each do |field|
|
|
167
187
|
column = fields_in_db[field]
|
|
168
188
|
connection.remove_column table_name, column.name
|
|
169
189
|
end
|
|
170
190
|
|
|
171
191
|
# Add fields to db new to schema
|
|
172
|
-
(
|
|
173
|
-
column =
|
|
192
|
+
(fields.keys - fields_in_db.keys).each do |field|
|
|
193
|
+
column = fields[field]
|
|
174
194
|
options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
|
|
175
|
-
options[:default] = column.default
|
|
176
|
-
options[:null] = column.null
|
|
195
|
+
options[:default] = column.default unless column.default.nil?
|
|
196
|
+
options[:null] = column.null unless column.null.nil?
|
|
177
197
|
connection.add_column table_name, column.name, column.type.to_sym, options
|
|
178
198
|
end
|
|
179
199
|
|
|
180
200
|
# Change attributes of existent columns
|
|
181
|
-
(
|
|
201
|
+
(fields.keys & fields_in_db.keys).each do |field|
|
|
182
202
|
if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
|
|
183
203
|
changed = false # flag
|
|
184
|
-
new_type =
|
|
204
|
+
new_type = fields[field].type.to_sym
|
|
185
205
|
new_attr = {}
|
|
186
206
|
|
|
187
207
|
# First, check if the field type changed
|
|
188
|
-
if
|
|
208
|
+
if fields[field].sql_type.to_s.downcase != fields_in_db[field].sql_type.to_s.downcase
|
|
209
|
+
logger.debug "[MiniRecord] Detected schema change for #{table_name}.#{field}#type from " +
|
|
210
|
+
"#{fields[field].sql_type.to_s.downcase.inspect} in #{fields_in_db[field].sql_type.to_s.downcase.inspect}" if logger
|
|
189
211
|
changed = true
|
|
190
212
|
end
|
|
191
213
|
|
|
192
214
|
# Special catch for precision/scale, since *both* must be specified together
|
|
193
215
|
# Always include them in the attr struct, but they'll only get applied if changed = true
|
|
194
|
-
new_attr[:precision] =
|
|
195
|
-
new_attr[:scale] =
|
|
216
|
+
new_attr[:precision] = fields[field][:precision]
|
|
217
|
+
new_attr[:scale] = fields[field][:scale]
|
|
218
|
+
|
|
219
|
+
# If we have precision this is also the limit
|
|
220
|
+
fields[field][:limit] ||= fields[field][:precision]
|
|
196
221
|
|
|
197
222
|
# Next, iterate through our extended attributes, looking for any differences
|
|
198
223
|
# This catches stuff like :null, :precision, etc
|
|
199
|
-
|
|
224
|
+
fields[field].each_pair do |att,value|
|
|
200
225
|
next if att == :type or att == :base or att == :name # special cases
|
|
201
|
-
if
|
|
226
|
+
value = true if att == :null && value.nil?
|
|
227
|
+
if value != fields_in_db[field].send(att)
|
|
228
|
+
logger.debug "[MiniRecord] Detected schema change for #{table_name}.#{field}##{att} "+
|
|
229
|
+
"from #{fields_in_db[field].send(att).inspect} in #{value.inspect}" if logger
|
|
202
230
|
new_attr[att] = value
|
|
203
231
|
changed = true
|
|
204
232
|
end
|
|
@@ -210,9 +238,7 @@ module MiniRecord
|
|
|
210
238
|
end
|
|
211
239
|
|
|
212
240
|
# Remove old index
|
|
213
|
-
|
|
214
|
-
indexes_in_db = connection.indexes(table_name).map(&:name)
|
|
215
|
-
(indexes_in_db - indexes.keys).each do |name|
|
|
241
|
+
(indexes_in_db.keys - indexes.keys).each do |name|
|
|
216
242
|
connection.remove_index(table_name, :name => name)
|
|
217
243
|
end
|
|
218
244
|
|
|
@@ -227,7 +253,6 @@ module MiniRecord
|
|
|
227
253
|
# Reload column information
|
|
228
254
|
reset_column_information
|
|
229
255
|
end
|
|
230
|
-
|
|
231
256
|
end
|
|
232
257
|
end # ClassMethods
|
|
233
258
|
end # AutoSchema
|
data/lib/mini_record/version.rb
CHANGED
data/mini_record.gemspec
CHANGED
data/test/helper.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
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 'pg', 'postgresql'
|
|
53
|
+
ActiveRecord::Base.establish_connection(:adapter => 'postgresql', :database => 'test', :user => 'postgres', :host => 'localhost')
|
|
54
|
+
else
|
|
55
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Some helpers to minitest
|
|
60
|
+
class MiniTest::Spec
|
|
61
|
+
def connection
|
|
62
|
+
ActiveRecord::Base.connection
|
|
63
|
+
end
|
|
64
|
+
alias :conn :connection
|
|
65
|
+
end
|
data/{spec → test}/models.rb
RENAMED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
|
2
|
-
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
|
|
3
|
-
|
|
4
1
|
class Person < ActiveRecord::Base
|
|
5
2
|
schema do |s|
|
|
6
3
|
s.string :name
|
|
@@ -65,3 +62,13 @@ end
|
|
|
65
62
|
class Activity < ActiveRecord::Base
|
|
66
63
|
belongs_to :author, :class_name => 'Account', :foreign_key => 'custom_id'
|
|
67
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,503 @@
|
|
|
1
|
+
require File.expand_path('../helper.rb', __FILE__)
|
|
2
|
+
|
|
3
|
+
describe MiniRecord do
|
|
4
|
+
|
|
5
|
+
before do
|
|
6
|
+
conn.tables.each { |table| silence_stream(STDERR) { conn.execute "DROP TABLE IF EXISTS #{table}" } }
|
|
7
|
+
ActiveRecord::Base.descendants.each { |klass| Object.send(:remove_const, klass.to_s) }
|
|
8
|
+
ActiveSupport::DescendantsTracker.direct_descendants(ActiveRecord::Base).clear
|
|
9
|
+
load File.expand_path('../models.rb', __FILE__)
|
|
10
|
+
ActiveRecord::Base.auto_upgrade!
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'has #schema inside model' do
|
|
14
|
+
assert_equal 'people', Person.table_name
|
|
15
|
+
assert_equal %w[created_at id name updated_at], Person.db_columns.sort
|
|
16
|
+
assert_equal Person.db_columns, Person.column_names.sort
|
|
17
|
+
assert_equal Person.schema_columns, Person.column_names.sort
|
|
18
|
+
|
|
19
|
+
# Check surname attribute
|
|
20
|
+
person = Person.create(:name => 'foo')
|
|
21
|
+
assert_equal 'foo', person.name
|
|
22
|
+
assert_raises(NoMethodError){ person.surname }
|
|
23
|
+
|
|
24
|
+
# Test the timestamp columns exist
|
|
25
|
+
assert_respond_to person, :created_at
|
|
26
|
+
assert_respond_to person, :updated_at
|
|
27
|
+
|
|
28
|
+
# Add a column without lost data
|
|
29
|
+
Person.class_eval do
|
|
30
|
+
schema do |p|
|
|
31
|
+
p.string :name
|
|
32
|
+
p.string :surname
|
|
33
|
+
end
|
|
34
|
+
timestamps
|
|
35
|
+
end
|
|
36
|
+
Person.auto_upgrade!
|
|
37
|
+
assert_equal 1, Person.count
|
|
38
|
+
|
|
39
|
+
person = Person.last
|
|
40
|
+
assert_equal 'foo', person.name
|
|
41
|
+
assert_nil person.surname
|
|
42
|
+
|
|
43
|
+
person.update_attribute(:surname, 'bar')
|
|
44
|
+
assert_equal %w[created_at id name surname updated_at], Person.db_columns.sort
|
|
45
|
+
|
|
46
|
+
# Remove a column without lost data
|
|
47
|
+
Person.class_eval do
|
|
48
|
+
schema do |p|
|
|
49
|
+
p.string :name
|
|
50
|
+
end
|
|
51
|
+
timestamps
|
|
52
|
+
end
|
|
53
|
+
Person.auto_upgrade!
|
|
54
|
+
person = Person.last
|
|
55
|
+
assert_equal 'foo', person.name
|
|
56
|
+
assert_raises(NoMethodError) { person.surname }
|
|
57
|
+
assert_equal %w[created_at id name updated_at], Person.db_columns
|
|
58
|
+
assert_equal Person.column_names.sort, Person.db_columns
|
|
59
|
+
assert_equal Person.column_names.sort, Person.schema_columns
|
|
60
|
+
|
|
61
|
+
# Change column without lost data
|
|
62
|
+
Person.class_eval do
|
|
63
|
+
schema do |p|
|
|
64
|
+
p.text :name
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
person = Person.last
|
|
68
|
+
assert_equal 'foo', person.name
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'has #key,col,property,attribute inside model' do
|
|
72
|
+
assert_equal Post.column_names.sort, Post.db_columns
|
|
73
|
+
assert_equal Category.column_names.sort, Category.schema_columns
|
|
74
|
+
|
|
75
|
+
# Check default properties
|
|
76
|
+
category = Category.create(:title => 'category')
|
|
77
|
+
post = Post.create(:title => 'foo', :body => 'bar', :category_id => category.id)
|
|
78
|
+
post = Post.first
|
|
79
|
+
assert_equal 'foo', post.title
|
|
80
|
+
assert_equal 'bar', post.body
|
|
81
|
+
assert_equal category, post.category
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Remove a column
|
|
85
|
+
Post.reset_table_definition!
|
|
86
|
+
Post.class_eval do
|
|
87
|
+
col :name
|
|
88
|
+
col :category, :as => :references
|
|
89
|
+
end
|
|
90
|
+
Post.auto_upgrade!
|
|
91
|
+
refute_includes %w[title body], Post.db_columns
|
|
92
|
+
|
|
93
|
+
post = Post.first
|
|
94
|
+
assert_nil post.name
|
|
95
|
+
assert_equal category, post.category
|
|
96
|
+
assert_raises(NoMethodError, ActiveModel::MissingAttributeError) { post.title }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'has indexes inside model' do
|
|
100
|
+
# Check indexes
|
|
101
|
+
assert Animal.db_indexes.size > 0
|
|
102
|
+
assert_equal Animal.db_indexes, Animal.indexes.keys.sort
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Remove an index
|
|
106
|
+
indexes_was = Animal.db_indexes
|
|
107
|
+
Animal.indexes.delete(indexes_was.pop)
|
|
108
|
+
Animal.auto_upgrade!
|
|
109
|
+
assert_equal indexes_was, Animal.indexes.keys
|
|
110
|
+
assert_equal indexes_was, Animal.db_indexes
|
|
111
|
+
|
|
112
|
+
# Add a new index
|
|
113
|
+
Animal.class_eval do
|
|
114
|
+
col :category, :as => :references, :index => true
|
|
115
|
+
end
|
|
116
|
+
Animal.auto_upgrade!
|
|
117
|
+
new_indexes = indexes_was + %w[index_animals_on_category_id]
|
|
118
|
+
assert_includes Animal.db_columns, 'category_id'
|
|
119
|
+
assert_equal new_indexes.sort, Animal.db_indexes
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'not add already defined indexes' do
|
|
123
|
+
class Foo < ActiveRecord::Base
|
|
124
|
+
index :customer_id, :unique => true, :name => 'by_customer'
|
|
125
|
+
belongs_to :customer
|
|
126
|
+
end
|
|
127
|
+
Foo.auto_upgrade!
|
|
128
|
+
assert_equal 1, Foo.db_indexes.size
|
|
129
|
+
assert_includes Foo.db_indexes, 'by_customer'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'works with STI' do
|
|
133
|
+
class Dog < Pet; end
|
|
134
|
+
class Cat < Pet; end
|
|
135
|
+
class Kitten < Cat; end
|
|
136
|
+
ActiveRecord::Base.auto_upgrade!
|
|
137
|
+
|
|
138
|
+
# Check inheritance column
|
|
139
|
+
assert_includes Pet.db_columns, "type"
|
|
140
|
+
|
|
141
|
+
# Now, let's we know if STI is working
|
|
142
|
+
Pet.create(:name => "foo")
|
|
143
|
+
Dog.create(:name => "bar")
|
|
144
|
+
Kitten.create(:name => 'foxy')
|
|
145
|
+
assert_equal 1, Dog.count
|
|
146
|
+
assert_equal 'bar', Dog.first.name
|
|
147
|
+
assert_equal 3, Pet.count
|
|
148
|
+
assert_equal %w[foo bar foxy], Pet.all.map(&:name)
|
|
149
|
+
assert_equal 'bar', Dog.first.name
|
|
150
|
+
|
|
151
|
+
# What's happen if we change schema?
|
|
152
|
+
assert_equal Dog.table_definition, Pet.table_definition
|
|
153
|
+
assert_equal Dog.indexes, Pet.indexes
|
|
154
|
+
|
|
155
|
+
Dog.class_eval do
|
|
156
|
+
col :bau
|
|
157
|
+
end
|
|
158
|
+
ActiveRecord::Base.auto_upgrade!
|
|
159
|
+
assert_includes Dog.schema_columns, 'bau'
|
|
160
|
+
assert_includes Pet.db_columns, 'bau'
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'works with custom inheritance column' do
|
|
164
|
+
class User < ActiveRecord::Base
|
|
165
|
+
col :name
|
|
166
|
+
col :surname
|
|
167
|
+
col :role
|
|
168
|
+
def self.inheritance_column; 'role'; end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
class Administrator < User; end
|
|
172
|
+
class Customer < User; end
|
|
173
|
+
|
|
174
|
+
User.auto_upgrade!
|
|
175
|
+
assert_equal 'role', User.inheritance_column
|
|
176
|
+
|
|
177
|
+
Administrator.create(:name => "Davide", :surname => 'DAddYE')
|
|
178
|
+
Customer.create(:name => "Foo", :surname => "Bar")
|
|
179
|
+
assert_equal 1, Administrator.count
|
|
180
|
+
assert_equal 'Davide', Administrator.first.name
|
|
181
|
+
assert_equal 1, Customer.count
|
|
182
|
+
assert_equal 'Foo', Customer.first.name
|
|
183
|
+
assert_equal 2, User.count
|
|
184
|
+
assert_equal 'Administrator', User.first.role
|
|
185
|
+
assert_equal 'Customer', User.last.role
|
|
186
|
+
assert_includes User.db_indexes, 'index_users_on_role'
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'allow multiple columns definitions' do
|
|
190
|
+
class Fake < ActiveRecord::Base
|
|
191
|
+
col :name, :surname
|
|
192
|
+
col :category, :group, :as => :references
|
|
193
|
+
end
|
|
194
|
+
Fake.auto_upgrade!
|
|
195
|
+
Fake.create(:name => 'foo', :surname => 'bar', :category_id => 1, :group_id => 2)
|
|
196
|
+
fake = Fake.first
|
|
197
|
+
assert_equal 'foo', fake.name
|
|
198
|
+
assert_equal 'bar', fake.surname
|
|
199
|
+
assert_equal 1, fake.category_id
|
|
200
|
+
assert_equal 2, fake.group_id
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'allow custom query' do
|
|
204
|
+
skip unless conn.adapter_name =~ /mysql/i
|
|
205
|
+
|
|
206
|
+
class Foo < ActiveRecord::Base
|
|
207
|
+
col :name, :as => "ENUM('foo','bar')"
|
|
208
|
+
end
|
|
209
|
+
Foo.auto_upgrade!
|
|
210
|
+
assert_match /ENUM/, Foo.queries
|
|
211
|
+
|
|
212
|
+
Foo.auto_upgrade!
|
|
213
|
+
assert_empty Foo.queries
|
|
214
|
+
assert_equal %w[id name], Foo.db_columns
|
|
215
|
+
assert_equal %w[id name], Foo.schema_columns
|
|
216
|
+
|
|
217
|
+
foo = Foo.create(:name => 'test')
|
|
218
|
+
assert_empty Foo.first.name
|
|
219
|
+
|
|
220
|
+
foo.update_attribute(:name, 'foo')
|
|
221
|
+
|
|
222
|
+
assert_equal 'foo', Foo.first.name
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
describe 'relation #belongs_to' do
|
|
226
|
+
it 'creates a column and index based on relation' do
|
|
227
|
+
Article.create(:title => 'Hello', :publisher_id => 1)
|
|
228
|
+
Article.first.tap do |a|
|
|
229
|
+
assert_equal 'Hello', a.title
|
|
230
|
+
assert_equal 1, a.publisher_id
|
|
231
|
+
end
|
|
232
|
+
assert_includes Article.db_indexes, 'index_articles_on_publisher_id'
|
|
233
|
+
|
|
234
|
+
# Ensure that associated field/index is not deleted on upgrade
|
|
235
|
+
Article.auto_upgrade!
|
|
236
|
+
assert_equal 1, Article.first.publisher_id
|
|
237
|
+
assert_includes Article.db_indexes, 'index_articles_on_publisher_id'
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'removes a column and index when relation is removed' do
|
|
241
|
+
class Foo < ActiveRecord::Base
|
|
242
|
+
key :name
|
|
243
|
+
belongs_to :image, :polymorphic => true
|
|
244
|
+
end
|
|
245
|
+
Foo.auto_upgrade!
|
|
246
|
+
assert_includes Foo.db_columns, 'name'
|
|
247
|
+
assert_includes Foo.db_columns, 'image_type'
|
|
248
|
+
assert_includes Foo.db_columns, 'image_id'
|
|
249
|
+
assert_includes Foo.db_indexes, 'index_foos_on_image_id_and_image_type'
|
|
250
|
+
|
|
251
|
+
Foo.class_eval do
|
|
252
|
+
reset_table_definition!
|
|
253
|
+
reflections.clear
|
|
254
|
+
indexes.clear
|
|
255
|
+
key :name
|
|
256
|
+
end
|
|
257
|
+
Foo.auto_upgrade!
|
|
258
|
+
assert_includes Foo.db_columns, 'name'
|
|
259
|
+
refute_includes Foo.db_columns, 'image_type'
|
|
260
|
+
refute_includes Foo.db_columns, 'image_id'
|
|
261
|
+
assert_empty Foo.db_indexes
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'creates columns and index based on polymorphic relation' do
|
|
265
|
+
Attachment.create(:name => 'Avatar', :attachable_id => 1, :attachable_type => 'Post')
|
|
266
|
+
Attachment.first.tap do |attachment|
|
|
267
|
+
assert_equal 'Avatar', attachment.name
|
|
268
|
+
assert_equal 1, attachment.attachable_id
|
|
269
|
+
assert_equal 'Post', attachment.attachable_type
|
|
270
|
+
end
|
|
271
|
+
index = 'index_attachments_on_attachable_id_and_attachable_type'
|
|
272
|
+
assert_includes Attachment.db_indexes, index
|
|
273
|
+
|
|
274
|
+
# Ensure that associated fields/indexes are not deleted on subsequent upgrade
|
|
275
|
+
Attachment.auto_upgrade!
|
|
276
|
+
assert_equal 1, Attachment.first.attachable_id
|
|
277
|
+
assert_equal 'Post', Attachment.first.attachable_type
|
|
278
|
+
assert_includes Attachment.db_indexes, index
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it 'should support :class_name' do
|
|
282
|
+
assert_includes Task.schema_columns, 'author_id'
|
|
283
|
+
assert_includes Task.db_columns, 'author_id'
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it 'should support :foreign_key' do
|
|
287
|
+
assert_includes Activity.schema_columns, 'custom_id'
|
|
288
|
+
assert_includes Activity.db_columns, 'custom_id'
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it 'should memonize in schema relationships' do
|
|
292
|
+
silence_stream(STDERR) { conn.create_table('foos') }
|
|
293
|
+
conn.add_column :foos, :name, :string
|
|
294
|
+
conn.add_column :foos, :bar_id, :integer
|
|
295
|
+
conn.add_index :foos, :bar_id
|
|
296
|
+
class Foo < ActiveRecord::Base
|
|
297
|
+
col :name
|
|
298
|
+
belongs_to :bar
|
|
299
|
+
end
|
|
300
|
+
assert_includes Foo.db_columns, 'name'
|
|
301
|
+
assert_includes Foo.db_columns, 'bar_id'
|
|
302
|
+
assert_includes Foo.db_indexes, 'index_foos_on_bar_id'
|
|
303
|
+
|
|
304
|
+
Foo.auto_upgrade!
|
|
305
|
+
assert_includes Foo.schema_columns, 'name'
|
|
306
|
+
assert_includes Foo.schema_columns, 'bar_id'
|
|
307
|
+
assert_includes Foo.indexes, 'index_foos_on_bar_id'
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'should add new columns without lost belongs_to schema' do
|
|
311
|
+
publisher = Publisher.create(:name => 'foo')
|
|
312
|
+
article = Article.create(:title => 'bar', :publisher => publisher)
|
|
313
|
+
assert article.valid?
|
|
314
|
+
assert_includes Article.indexes, 'index_articles_on_publisher_id'
|
|
315
|
+
|
|
316
|
+
# Here we perform a schema change
|
|
317
|
+
Article.key :body
|
|
318
|
+
Article.auto_upgrade!
|
|
319
|
+
article.reload
|
|
320
|
+
assert_nil article.body
|
|
321
|
+
|
|
322
|
+
article.update_attribute(:body, 'null')
|
|
323
|
+
assert_equal 'null', article.body
|
|
324
|
+
|
|
325
|
+
# Finally check the index existance
|
|
326
|
+
assert_includes Article.db_indexes, 'index_articles_on_publisher_id'
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
it 'should not override previous defined column relation' do
|
|
330
|
+
class Foo < ActiveRecord::Base
|
|
331
|
+
key :user, :as => :references, :null => false, :limit => 4
|
|
332
|
+
belongs_to :user
|
|
333
|
+
end
|
|
334
|
+
Foo.auto_upgrade!
|
|
335
|
+
assert_equal 4, Foo.db_fields[:user_id].limit
|
|
336
|
+
assert_equal false, Foo.db_fields[:user_id].null
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
describe 'relation #habtm' do
|
|
341
|
+
it 'creates a join table with indexes for has_and_belongs_to_many relations' do
|
|
342
|
+
tables = Tool.connection.tables
|
|
343
|
+
assert_includes tables, 'purposes_tools'
|
|
344
|
+
|
|
345
|
+
index = 'index_purposes_tools_on_tool_id_and_purpose_id'
|
|
346
|
+
assert_includes Tool.connection.indexes('purposes_tools').map(&:name), index
|
|
347
|
+
|
|
348
|
+
# Ensure that join table is not deleted on subsequent upgrade
|
|
349
|
+
Tool.auto_upgrade!
|
|
350
|
+
assert_includes tables, 'purposes_tools'
|
|
351
|
+
assert_includes Tool.connection.indexes('purposes_tools').map(&:name), index
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
it 'drops join table if has_and_belongs_to_many relation is deleted' do
|
|
355
|
+
Tool.schema_tables.delete('purposes_tools')
|
|
356
|
+
refute_includes ActiveRecord::Base.schema_tables, 'purposes_tools'
|
|
357
|
+
|
|
358
|
+
ActiveRecord::Base.clear_tables!
|
|
359
|
+
refute_includes Tool.connection.tables, 'purposes_tools'
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'has_and_belongs_to_many with custom join_table and foreign keys' do
|
|
363
|
+
class Foo < ActiveRecord::Base
|
|
364
|
+
has_and_belongs_to_many :watchers, :join_table => :watching, :foreign_key => :custom_foo_id, :association_foreign_key => :customer_id
|
|
365
|
+
end
|
|
366
|
+
Foo.auto_upgrade!
|
|
367
|
+
assert_includes conn.tables, 'watching'
|
|
368
|
+
|
|
369
|
+
cols = conn.columns('watching').map(&:name)
|
|
370
|
+
refute_includes cols, 'id'
|
|
371
|
+
assert_includes cols, 'custom_foo_id'
|
|
372
|
+
assert_includes cols, 'customer_id'
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
it 'creates a join table with indexes with long indexes names' do
|
|
376
|
+
class Foo < ActiveRecord::Base
|
|
377
|
+
has_and_belongs_to_many :people, :join_table => :long_people,
|
|
378
|
+
:foreign_key => :custom_long_long_long_long_id,
|
|
379
|
+
:association_foreign_key => :customer_super_long_very_long_trust_me_id
|
|
380
|
+
end
|
|
381
|
+
Foo.auto_upgrade!
|
|
382
|
+
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]
|
|
383
|
+
assert_includes conn.tables, 'people'
|
|
384
|
+
assert_includes conn.indexes(:long_people).map(&:name), index_name
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
it 'adds unique index' do
|
|
388
|
+
page = Page.create(:title => 'Foo')
|
|
389
|
+
photogallery = Photogallery.create(:title => 'Bar')
|
|
390
|
+
assert photogallery.valid?
|
|
391
|
+
|
|
392
|
+
photogallery.pages << page
|
|
393
|
+
refute_empty Photogallery.queries
|
|
394
|
+
assert_includes photogallery.reload.pages, page
|
|
395
|
+
assert_raises(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid){ photogallery.pages << page }
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
it 'should add multiple index' do
|
|
400
|
+
class Foo < ActiveRecord::Base
|
|
401
|
+
key :name, :surname, :index => true
|
|
402
|
+
end
|
|
403
|
+
Foo.auto_upgrade!
|
|
404
|
+
assert_includes Foo.db_indexes, 'index_foos_on_name'
|
|
405
|
+
assert_includes Foo.db_indexes, 'index_foos_on_surname'
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
it 'should create a unique index' do
|
|
409
|
+
class Foo < ActiveRecord::Base
|
|
410
|
+
key :name, :surname
|
|
411
|
+
add_index([:name, :surname], :unique => true)
|
|
412
|
+
end
|
|
413
|
+
Foo.auto_upgrade!
|
|
414
|
+
db_indexes = Foo.connection.indexes('foos')[0]
|
|
415
|
+
assert_equal 'index_foos_on_name_and_surname', db_indexes.name
|
|
416
|
+
assert db_indexes.unique
|
|
417
|
+
assert_equal %w[name surname], db_indexes.columns.sort
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
it 'should change #limit' do
|
|
421
|
+
class Foo < ActiveRecord::Base
|
|
422
|
+
key :number, :as => :integer
|
|
423
|
+
key :string, :limit => 100
|
|
424
|
+
end
|
|
425
|
+
Foo.auto_upgrade!
|
|
426
|
+
assert_match /CREATE TABLE/, Foo.queries
|
|
427
|
+
|
|
428
|
+
Foo.auto_upgrade!
|
|
429
|
+
refute_match /alter/i, Foo.queries
|
|
430
|
+
|
|
431
|
+
# According to this:
|
|
432
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L476-487
|
|
433
|
+
Foo.key :number, :as => :integer, :limit => 4
|
|
434
|
+
Foo.auto_upgrade!
|
|
435
|
+
case conn.adapter_name
|
|
436
|
+
when /sqlite/i
|
|
437
|
+
# In sqlite there is a difference between limit: 4 and limit: 11
|
|
438
|
+
assert_match 'altered', Foo.queries
|
|
439
|
+
assert_equal 4, Foo.db_fields[:number].limit
|
|
440
|
+
assert_equal 4, Foo.schema_fields[:number].limit
|
|
441
|
+
when /mysql/i
|
|
442
|
+
# In mysql according to this: http://goo.gl/bjZE7 limit: 4 is same of limit:11
|
|
443
|
+
assert_empty Foo.queries
|
|
444
|
+
assert_equal 4, Foo.db_fields[:number].limit
|
|
445
|
+
assert_equal 4, Foo.schema_fields[:number].limit
|
|
446
|
+
when /postgres/i
|
|
447
|
+
# In postgres limit: 4 will be translated to nil
|
|
448
|
+
assert_match /ALTER COLUMN "number" TYPE integer$/, Foo.queries
|
|
449
|
+
assert_equal nil, Foo.db_fields[:number].limit
|
|
450
|
+
assert_equal 4, Foo.schema_fields[:number].limit
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Change limit to string
|
|
454
|
+
Foo.key :string, :limit => 255
|
|
455
|
+
Foo.auto_upgrade!
|
|
456
|
+
refute_empty Foo.queries
|
|
457
|
+
assert_equal 255, Foo.db_fields[:string].limit
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
it 'should change #null' do
|
|
461
|
+
class Foo < ActiveRecord::Base
|
|
462
|
+
key :string
|
|
463
|
+
end
|
|
464
|
+
Foo.auto_upgrade!
|
|
465
|
+
assert Foo.db_fields[:string].null
|
|
466
|
+
|
|
467
|
+
# Same as above
|
|
468
|
+
Foo.key :string, :null => true
|
|
469
|
+
Foo.auto_upgrade!
|
|
470
|
+
refute_match /alter/i, Foo.queries
|
|
471
|
+
assert Foo.db_fields[:string].null
|
|
472
|
+
|
|
473
|
+
Foo.key :string, :null => nil
|
|
474
|
+
Foo.auto_upgrade!
|
|
475
|
+
refute_match /alter/i, Foo.queries
|
|
476
|
+
assert Foo.db_fields[:string].null
|
|
477
|
+
|
|
478
|
+
Foo.key :string, :null => false
|
|
479
|
+
Foo.auto_upgrade!
|
|
480
|
+
assert_match /alter/i, Foo.queries
|
|
481
|
+
refute Foo.db_fields[:string].null
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
it 'should change #scale #precision' do
|
|
485
|
+
class Foo < ActiveRecord::Base
|
|
486
|
+
field :currency, :as => :decimal, :precision => 8, :scale => 2
|
|
487
|
+
end
|
|
488
|
+
Foo.auto_upgrade!
|
|
489
|
+
assert_equal 8, Foo.db_fields[:currency].precision
|
|
490
|
+
assert_equal 2, Foo.db_fields[:currency].scale
|
|
491
|
+
assert_equal 8, Foo.db_fields[:currency].limit
|
|
492
|
+
|
|
493
|
+
Foo.auto_upgrade!
|
|
494
|
+
refute_match /alter/i, Foo.queries
|
|
495
|
+
|
|
496
|
+
Foo.field :currency, :as => :decimal, :precision => 4, :scale => 2, :limit => 5
|
|
497
|
+
Foo.auto_upgrade!
|
|
498
|
+
assert_match /alter/i, Foo.queries
|
|
499
|
+
assert_equal 4, Foo.db_fields[:currency].precision
|
|
500
|
+
assert_equal 2, Foo.db_fields[:currency].scale
|
|
501
|
+
assert_equal 4, Foo.db_fields[:currency].limit
|
|
502
|
+
end
|
|
503
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mini_record
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 17
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 3
|
|
9
|
-
-
|
|
10
|
-
version: 0.3.
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.3.1
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Davide D'Agostino
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2012-
|
|
18
|
+
date: 2012-02-17 00:00:00 Z
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
21
21
|
name: activerecord
|
|
@@ -23,7 +23,7 @@ dependencies:
|
|
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
24
|
none: false
|
|
25
25
|
requirements:
|
|
26
|
-
- -
|
|
26
|
+
- - ">="
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
28
|
hash: 15
|
|
29
29
|
segments:
|
|
@@ -55,9 +55,9 @@ files:
|
|
|
55
55
|
- lib/mini_record/auto_schema.rb
|
|
56
56
|
- lib/mini_record/version.rb
|
|
57
57
|
- mini_record.gemspec
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
58
|
+
- test/helper.rb
|
|
59
|
+
- test/models.rb
|
|
60
|
+
- test/test_mini_record.rb
|
|
61
61
|
homepage: https://github.com/DAddYE/mini_record
|
|
62
62
|
licenses: []
|
|
63
63
|
|
|
@@ -92,7 +92,7 @@ signing_key:
|
|
|
92
92
|
specification_version: 3
|
|
93
93
|
summary: MiniRecord is a micro gem that allow you to write schema inside your model as you can do in DataMapper.
|
|
94
94
|
test_files:
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
95
|
+
- test/helper.rb
|
|
96
|
+
- test/models.rb
|
|
97
|
+
- test/test_mini_record.rb
|
|
98
98
|
has_rdoc:
|
data/spec/mini_record_spec.rb
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
require File.expand_path('../spec_helper.rb', __FILE__)
|
|
2
|
-
|
|
3
|
-
describe MiniRecord do
|
|
4
|
-
|
|
5
|
-
before do
|
|
6
|
-
ActiveRecord::Base.descendants.each { |klass| Object.send(:remove_const, klass.to_s) }
|
|
7
|
-
ActiveSupport::DescendantsTracker.direct_descendants(ActiveRecord::Base).clear
|
|
8
|
-
load File.expand_path('../models.rb', __FILE__)
|
|
9
|
-
ActiveRecord::Base.auto_upgrade!
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
it 'has #schema inside model' do
|
|
13
|
-
Person.table_name.must_equal 'people'
|
|
14
|
-
Person.db_columns.sort.must_equal %w[created_at id name updated_at]
|
|
15
|
-
Person.column_names.sort.must_equal Person.db_columns.sort
|
|
16
|
-
Person.column_names.sort.must_equal Person.schema_columns.sort
|
|
17
|
-
person = Person.create(:name => 'foo')
|
|
18
|
-
person.name.must_equal 'foo'
|
|
19
|
-
proc { person.surname }.must_raise NoMethodError
|
|
20
|
-
|
|
21
|
-
# Test the timestamp columns exist
|
|
22
|
-
person.must_respond_to :created_at
|
|
23
|
-
person.must_respond_to :updated_at
|
|
24
|
-
|
|
25
|
-
# Add a column without lost data
|
|
26
|
-
Person.class_eval do
|
|
27
|
-
schema do |p|
|
|
28
|
-
p.string :name
|
|
29
|
-
p.string :surname
|
|
30
|
-
end
|
|
31
|
-
timestamps
|
|
32
|
-
end
|
|
33
|
-
Person.auto_upgrade!
|
|
34
|
-
Person.count.must_equal 1
|
|
35
|
-
person = Person.last
|
|
36
|
-
person.name.must_equal 'foo'
|
|
37
|
-
person.surname.must_be_nil
|
|
38
|
-
person.update_attribute(:surname, 'bar')
|
|
39
|
-
Person.db_columns.sort.must_equal %w[created_at id name surname updated_at]
|
|
40
|
-
# Person.column_names.must_equal Person.db_columns
|
|
41
|
-
|
|
42
|
-
# Remove a column without lost data
|
|
43
|
-
Person.class_eval do
|
|
44
|
-
schema do |p|
|
|
45
|
-
p.string :name
|
|
46
|
-
end
|
|
47
|
-
timestamps
|
|
48
|
-
end
|
|
49
|
-
Person.auto_upgrade!
|
|
50
|
-
person = Person.last
|
|
51
|
-
person.name.must_equal 'foo'
|
|
52
|
-
proc { person.surname }.must_raise NoMethodError
|
|
53
|
-
Person.db_columns.sort.must_equal %w[created_at id name updated_at]
|
|
54
|
-
Person.column_names.sort.must_equal Person.db_columns.sort
|
|
55
|
-
Person.column_names.sort.must_equal Person.schema_columns.sort
|
|
56
|
-
|
|
57
|
-
# Change column without lost data
|
|
58
|
-
Person.class_eval do
|
|
59
|
-
schema do |p|
|
|
60
|
-
p.text :name
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
person = Person.last
|
|
64
|
-
person.name.must_equal 'foo'
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it 'has #key,col,property,attribute inside model' do
|
|
68
|
-
Post.column_names.sort.must_equal Post.db_columns
|
|
69
|
-
Category.column_names.sort.must_equal Category.schema_columns
|
|
70
|
-
|
|
71
|
-
# Check default properties
|
|
72
|
-
category = Category.create(:title => 'category')
|
|
73
|
-
post = Post.create(:title => 'foo', :body => 'bar', :category_id => category.id)
|
|
74
|
-
post = Post.first
|
|
75
|
-
post.title.must_equal 'foo'
|
|
76
|
-
post.body.must_equal 'bar'
|
|
77
|
-
post.category.must_equal category
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
# Remove a column
|
|
81
|
-
Post.reset_table_definition!
|
|
82
|
-
Post.class_eval do
|
|
83
|
-
col :name
|
|
84
|
-
col :category, :as => :references
|
|
85
|
-
end
|
|
86
|
-
Post.auto_upgrade!
|
|
87
|
-
post = Post.first
|
|
88
|
-
post.name.must_be_nil
|
|
89
|
-
post.category.must_equal category
|
|
90
|
-
proc { post.title }.must_raise ActiveModel::MissingAttributeError
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
it 'has indexes inside model' do
|
|
94
|
-
# Check indexes
|
|
95
|
-
Animal.db_indexes.size.must_be :>, 0
|
|
96
|
-
Animal.db_indexes.must_equal Animal.indexes.keys.sort
|
|
97
|
-
|
|
98
|
-
indexes_was = Animal.db_indexes
|
|
99
|
-
|
|
100
|
-
# Remove an index
|
|
101
|
-
Animal.indexes.delete(indexes_was.pop)
|
|
102
|
-
Animal.auto_upgrade!
|
|
103
|
-
Animal.indexes.keys.sort.must_equal indexes_was
|
|
104
|
-
Animal.db_indexes.must_equal indexes_was
|
|
105
|
-
|
|
106
|
-
# Add a new index
|
|
107
|
-
Animal.class_eval do
|
|
108
|
-
col :category, :as => :references, :index => true
|
|
109
|
-
end
|
|
110
|
-
Animal.auto_upgrade!
|
|
111
|
-
Animal.db_columns.must_include "category_id"
|
|
112
|
-
Animal.db_indexes.must_equal((indexes_was << "index_animals_on_category_id").sort)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
it 'works with STI' do
|
|
116
|
-
class Dog < Pet; end
|
|
117
|
-
class Cat < Pet; end
|
|
118
|
-
ActiveRecord::Base.auto_upgrade!
|
|
119
|
-
|
|
120
|
-
# Check inheritance column
|
|
121
|
-
Pet.db_columns.must_include "type"
|
|
122
|
-
|
|
123
|
-
# Now, let's we know if STI is working
|
|
124
|
-
Pet.create(:name => "foo")
|
|
125
|
-
Dog.create(:name => "bar")
|
|
126
|
-
Dog.count.must_equal 1
|
|
127
|
-
Dog.first.name.must_equal "bar"
|
|
128
|
-
Pet.count.must_equal 2
|
|
129
|
-
Pet.all.map(&:name).must_equal ["foo", "bar"]
|
|
130
|
-
|
|
131
|
-
# Check that this doesn't break things
|
|
132
|
-
Dog.first.name.must_equal "bar"
|
|
133
|
-
|
|
134
|
-
# What's happen if we change schema?
|
|
135
|
-
Dog.table_definition.must_equal Pet.table_definition
|
|
136
|
-
Dog.indexes.must_equal Pet.indexes
|
|
137
|
-
Dog.class_eval do
|
|
138
|
-
col :bau
|
|
139
|
-
end
|
|
140
|
-
ActiveRecord::Base.auto_upgrade!
|
|
141
|
-
Dog.schema_columns.must_include "bau"
|
|
142
|
-
Pet.db_columns.must_include "bau"
|
|
143
|
-
# Dog.new.must_respond_to :bau
|
|
144
|
-
# Cat.new.must_respond_to :bau
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
it 'works with custom inheritance column' do
|
|
148
|
-
class User < ActiveRecord::Base
|
|
149
|
-
col :name
|
|
150
|
-
col :surname
|
|
151
|
-
col :role
|
|
152
|
-
def self.inheritance_column; 'role'; end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
class Administrator < User; end
|
|
156
|
-
class Customer < User; end
|
|
157
|
-
|
|
158
|
-
User.auto_upgrade!
|
|
159
|
-
User.inheritance_column.must_equal 'role'
|
|
160
|
-
Administrator.create(:name => "Davide", :surname => 'DAddYE')
|
|
161
|
-
Customer.create(:name => "Foo", :surname => "Bar")
|
|
162
|
-
Administrator.count.must_equal 1
|
|
163
|
-
Administrator.first.name.must_equal "Davide"
|
|
164
|
-
Customer.count.must_equal 1
|
|
165
|
-
Customer.first.name.must_equal "Foo"
|
|
166
|
-
User.count.must_equal 2
|
|
167
|
-
User.first.role.must_equal "Administrator"
|
|
168
|
-
User.last.role.must_equal "Customer"
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
it 'allow multiple columns definitions' do
|
|
172
|
-
class Fake < ActiveRecord::Base
|
|
173
|
-
col :name, :surname
|
|
174
|
-
col :category, :group, :as => :references
|
|
175
|
-
end
|
|
176
|
-
Fake.auto_upgrade!
|
|
177
|
-
Fake.create(:name => 'foo', :surname => 'bar', :category_id => 1, :group_id => 2)
|
|
178
|
-
fake = Fake.first
|
|
179
|
-
fake.name.must_equal 'foo'
|
|
180
|
-
fake.surname.must_equal 'bar'
|
|
181
|
-
fake.category_id.must_equal 1
|
|
182
|
-
fake.group_id.must_equal 2
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
it 'creates a column and index based on belongs_to relation' do
|
|
186
|
-
Article.create(:title => 'Hello', :publisher_id => 1)
|
|
187
|
-
Article.first.tap do |a|
|
|
188
|
-
a.title.must_equal 'Hello'
|
|
189
|
-
a.publisher_id.must_equal 1
|
|
190
|
-
end
|
|
191
|
-
Article.db_indexes.must_include 'index_articles_on_publisher_id'
|
|
192
|
-
# Ensure that associated field/index is not deleted on upgrade
|
|
193
|
-
Article.auto_upgrade!
|
|
194
|
-
Article.first.publisher_id.must_equal 1
|
|
195
|
-
Article.db_indexes.must_include 'index_articles_on_publisher_id'
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
it 'removes a column and index when belongs_to relation is removed' do
|
|
199
|
-
class Foo < ActiveRecord::Base
|
|
200
|
-
key :name
|
|
201
|
-
belongs_to :image, :polymorphic => true
|
|
202
|
-
end
|
|
203
|
-
Foo.auto_upgrade!
|
|
204
|
-
Foo.db_columns.must_include 'name'
|
|
205
|
-
Foo.db_columns.must_include 'image_type'
|
|
206
|
-
Foo.db_columns.must_include 'image_id'
|
|
207
|
-
Foo.db_indexes.must_include 'index_foos_on_image_id_and_image_type'
|
|
208
|
-
Foo.class_eval do
|
|
209
|
-
reset_table_definition!
|
|
210
|
-
reflections.clear
|
|
211
|
-
indexes.clear
|
|
212
|
-
key :name
|
|
213
|
-
end
|
|
214
|
-
Foo.auto_upgrade!
|
|
215
|
-
Foo.db_columns.must_include 'name'
|
|
216
|
-
Foo.db_columns.wont_include 'image_type'
|
|
217
|
-
Foo.db_columns.wont_include 'image_id'
|
|
218
|
-
Foo.db_indexes.must_be_empty
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
it 'creates columns and index based on belongs_to polymorphic relation' do
|
|
222
|
-
Attachment.create(:name => 'Avatar', :attachable_id => 1, :attachable_type => 'Post')
|
|
223
|
-
Attachment.first.tap do |attachment|
|
|
224
|
-
attachment.name.must_equal 'Avatar'
|
|
225
|
-
attachment.attachable_id.must_equal 1
|
|
226
|
-
attachment.attachable_type.must_equal 'Post'
|
|
227
|
-
end
|
|
228
|
-
index = 'index_attachments_on_attachable_id_and_attachable_type'
|
|
229
|
-
Attachment.db_indexes.must_include index
|
|
230
|
-
# Ensure that associated fields/indexes are not deleted on subsequent upgrade
|
|
231
|
-
Attachment.auto_upgrade!
|
|
232
|
-
Attachment.first.attachable_id.must_equal 1
|
|
233
|
-
Attachment.first.attachable_type.must_equal 'Post'
|
|
234
|
-
Attachment.db_indexes.must_include index
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
it 'creates a join table with indexes for has_and_belongs_to_many relations' do
|
|
238
|
-
tables = Tool.connection.tables
|
|
239
|
-
tables.must_include('purposes_tools')
|
|
240
|
-
index = 'index_purposes_tools_on_purposes_tool_id_and_purpose_id'
|
|
241
|
-
Tool.connection.indexes('purposes_tools').map(&:name).must_include index
|
|
242
|
-
# Ensure that join table is not deleted on subsequent upgrade
|
|
243
|
-
Tool.auto_upgrade!
|
|
244
|
-
tables.must_include('purposes_tools')
|
|
245
|
-
Tool.connection.indexes('purposes_tools').map(&:name).must_include index
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
it 'drops join table if has_and_belongs_to_many relation is deleted' do
|
|
249
|
-
Tool.schema_tables.delete('purposes_tools')
|
|
250
|
-
ActiveRecord::Base.schema_tables.wont_include('purposes_tools')
|
|
251
|
-
ActiveRecord::Base.clear_tables!
|
|
252
|
-
Tool.connection.tables.wont_include('purposes_tools')
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
it 'has_and_belongs_to_many with custom join_table and foreign keys' do
|
|
256
|
-
class Foo < ActiveRecord::Base
|
|
257
|
-
has_and_belongs_to_many :watchers, :join_table => :watchers, :foreign_key => :custom_foo_id, :association_foreign_key => :customer_id
|
|
258
|
-
end
|
|
259
|
-
Foo.auto_upgrade!
|
|
260
|
-
conn = ActiveRecord::Base.connection
|
|
261
|
-
conn.tables.must_include 'watchers'
|
|
262
|
-
cols = conn.columns('watchers').map(&:name)
|
|
263
|
-
cols.wont_include 'id'
|
|
264
|
-
cols.must_include 'custom_foo_id'
|
|
265
|
-
cols.must_include 'customer_id'
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
it 'should support #belongs_to with :class_name' do
|
|
269
|
-
Task.schema_columns.must_include 'author_id'
|
|
270
|
-
Task.db_columns.must_include 'author_id'
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
it 'should support #belongs_to with :foreign_key' do
|
|
274
|
-
Activity.schema_columns.must_include 'custom_id'
|
|
275
|
-
Activity.db_columns.must_include 'custom_id'
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
it 'should memonize in schema relationships' do
|
|
279
|
-
conn = ActiveRecord::Base.connection
|
|
280
|
-
conn.create_table('foos')
|
|
281
|
-
conn.add_column :foos, :name, :string
|
|
282
|
-
conn.add_column :foos, :bar_id, :integer
|
|
283
|
-
conn.add_index :foos, :bar_id
|
|
284
|
-
class Foo < ActiveRecord::Base
|
|
285
|
-
col :name
|
|
286
|
-
belongs_to :bar
|
|
287
|
-
end
|
|
288
|
-
Foo.db_columns.must_include 'name'
|
|
289
|
-
Foo.db_columns.must_include 'bar_id'
|
|
290
|
-
Foo.db_indexes.must_include 'index_foos_on_bar_id'
|
|
291
|
-
Foo.auto_upgrade!
|
|
292
|
-
Foo.schema_columns.must_include 'name'
|
|
293
|
-
Foo.schema_columns.must_include 'bar_id'
|
|
294
|
-
Foo.indexes.must_include 'index_foos_on_bar_id'
|
|
295
|
-
end
|
|
296
|
-
end
|
data/spec/spec_helper.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
require 'rubygems' unless defined?(Gem)
|
|
2
|
-
require 'bundler/setup'
|
|
3
|
-
require 'mini_record'
|
|
4
|
-
require 'minitest/autorun'
|
|
5
|
-
|
|
6
|
-
module SpecHelper
|
|
7
|
-
module ClassMethods
|
|
8
|
-
def db_columns
|
|
9
|
-
connection.columns(table_name).map(&:name).sort
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def db_indexes
|
|
13
|
-
connection.indexes(table_name).map(&:name).sort
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def schema_columns
|
|
17
|
-
table_definition.columns.map { |c| c.name.to_s }.sort
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def reset!
|
|
21
|
-
reset
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
ActiveRecord::Base.extend(SpecHelper::ClassMethods)
|