mini_record 0.2.1 → 0.3.0.a
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.travis.yml +10 -0
- data/Gemfile +1 -0
- data/README.md +38 -7
- data/lib/mini_record/auto_schema.rb +136 -78
- data/lib/mini_record/version.rb +1 -1
- data/mini_record.gemspec +1 -1
- data/spec/mini_record_spec.rb +130 -26
- data/spec/models.rb +38 -31
- data/spec/spec_helper.rb +22 -0
- metadata +19 -16
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[![Build Status](https://secure.travis-ci.org/DAddYE/mini_record.png)](http://travis-ci.org/DAddYE/mini_record)
|
2
|
+
|
3
|
+
|
1
4
|
MiniRecord is a micro extension for our `ActiveRecord` gem.
|
2
5
|
With MiniRecord you can add the ability to create columns outside the default `schema.rb`, directly
|
3
6
|
in your **model** in a similar way that should know in others projects
|
@@ -9,7 +12,7 @@ My inspiration come from this handy [project](https://github.com/pjhyett/auto_mi
|
|
9
12
|
|
10
13
|
* Define columns/properties inside your model
|
11
14
|
* Perform migrations automatically
|
12
|
-
* Auto upgrade your schema, so if you know what you are doing you don't
|
15
|
+
* Auto upgrade your schema, so if you know what you are doing you don't lose your existing data!
|
13
16
|
* Add, Remove, Change Columns; Add, Remove, Change indexes
|
14
17
|
|
15
18
|
## Instructions
|
@@ -48,8 +51,8 @@ Instead of `:as => :my_type` you can use `:type => :my_type`
|
|
48
51
|
Option `:as` or `:type` if not provided is `:string` by default, you can use all ActiveRecord types:
|
49
52
|
|
50
53
|
``` rb
|
51
|
-
:primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time,
|
52
|
-
:
|
54
|
+
:primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time,
|
55
|
+
:date, :binary, :boolean, :references, :belongs_to, :timestamp
|
53
56
|
```
|
54
57
|
|
55
58
|
You can provide others ActiveRecord options like:
|
@@ -64,7 +67,7 @@ class Foo < ActiveRecord::Base
|
|
64
67
|
end
|
65
68
|
```
|
66
69
|
|
67
|
-
See [ActiveRecord::TableDefinition](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html)
|
70
|
+
See [ActiveRecord::TableDefinition](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html)
|
68
71
|
for more details.
|
69
72
|
|
70
73
|
Finally, when you execute `MyModel.auto_upgrade!`, missing columns, indexes and tables will be created on the fly.
|
@@ -74,6 +77,34 @@ Indexes and columns present in the db but **not** in your model schema will be *
|
|
74
77
|
|
75
78
|
MiniRecord as ActiveRecord support STI plus some goodness, see our specs for more details.
|
76
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 (polymorphic)
|
93
|
+
```ruby
|
94
|
+
class Address < ActiveRecord::Base
|
95
|
+
belongs_to :addressable, :polymorphic => true
|
96
|
+
end
|
97
|
+
```
|
98
|
+
Will result in addressable id and type columns with composite indexes `add_index(:addresses), [:addressable_id, :addressable_type]`
|
99
|
+
|
100
|
+
#### habtm
|
101
|
+
```ruby
|
102
|
+
class Address < ActiveRecord::Base
|
103
|
+
has_and_belongs_to_many :people
|
104
|
+
end
|
105
|
+
```
|
106
|
+
Will generate a "addresses_people" join table and index the id columns
|
107
|
+
|
77
108
|
### Adding a new column
|
78
109
|
|
79
110
|
Super easy, open your model and just add it:
|
@@ -136,8 +167,8 @@ end
|
|
136
167
|
|
137
168
|
## Warnings
|
138
169
|
|
139
|
-
This software is not
|
140
|
-
|
170
|
+
This software is not super well tested in a production project.
|
171
|
+
Im stated to using it in two customer's projects without any problem.
|
141
172
|
|
142
173
|
## Author
|
143
174
|
|
@@ -157,4 +188,4 @@ The above copyright notice and this permission notice shall be included in all c
|
|
157
188
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
158
189
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM,
|
159
190
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
160
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
191
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -6,6 +6,10 @@ module MiniRecord
|
|
6
6
|
|
7
7
|
module ClassMethods
|
8
8
|
|
9
|
+
def schema_tables
|
10
|
+
@@_schema_tables ||= []
|
11
|
+
end
|
12
|
+
|
9
13
|
def table_definition
|
10
14
|
return superclass.table_definition unless superclass == ActiveRecord::Base
|
11
15
|
|
@@ -31,12 +35,12 @@ module MiniRecord
|
|
31
35
|
table_definition.send(type, column_name, options)
|
32
36
|
column_name = table_definition.columns[-1].name
|
33
37
|
case index_name = options.delete(:index)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
when Hash
|
39
|
+
add_index(options.delete(:column) || column_name, index_name)
|
40
|
+
when TrueClass
|
41
|
+
add_index(column_name)
|
42
|
+
when String, Symbol, Array
|
43
|
+
add_index(index_name)
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
@@ -45,6 +49,10 @@ module MiniRecord
|
|
45
49
|
alias :field :col
|
46
50
|
alias :attribute :col
|
47
51
|
|
52
|
+
def timestamps
|
53
|
+
col :created_at, :updated_at, :as => :datetime
|
54
|
+
end
|
55
|
+
|
48
56
|
def reset_table_definition!
|
49
57
|
@_table_definition = nil
|
50
58
|
end
|
@@ -74,99 +82,149 @@ module MiniRecord
|
|
74
82
|
false
|
75
83
|
end
|
76
84
|
|
85
|
+
def clear_tables!
|
86
|
+
# Drop unsued tables
|
87
|
+
(connection.tables - schema_tables).each do |name|
|
88
|
+
connection.drop_table(name)
|
89
|
+
schema_tables.delete(name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
77
93
|
def auto_upgrade!
|
78
94
|
return unless connection?
|
79
95
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
connection.
|
86
|
-
|
87
|
-
|
96
|
+
if self == ActiveRecord::Base
|
97
|
+
descendants.each(&:auto_upgrade!)
|
98
|
+
clear_tables!
|
99
|
+
else
|
100
|
+
# Table doesn't exist, create it
|
101
|
+
unless connection.tables.include?(table_name)
|
102
|
+
# TODO: create_table options
|
103
|
+
class << connection; attr_accessor :table_definition; end unless connection.respond_to?(:table_definition=)
|
104
|
+
connection.table_definition = table_definition
|
105
|
+
connection.create_table(table_name)
|
106
|
+
connection.table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
|
107
|
+
end
|
88
108
|
|
89
|
-
|
90
|
-
|
91
|
-
hash[column.name] = column
|
92
|
-
hash
|
93
|
-
end
|
109
|
+
# Add this to our schema tables
|
110
|
+
schema_tables << table_name unless schema_tables.include?(table_name)
|
94
111
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
100
117
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
118
|
+
# Generate fields from associations
|
119
|
+
if reflect_on_all_associations.any?
|
120
|
+
reflect_on_all_associations.each do |association|
|
121
|
+
id_key = if association.options[:foreign_key]
|
122
|
+
association.options[:foreign_key]
|
123
|
+
else
|
124
|
+
"#{association.name.to_s}_id".to_sym
|
125
|
+
end
|
126
|
+
type_key = "#{association.name.to_s}_type".to_sym
|
127
|
+
case association.macro
|
128
|
+
when :belongs_to
|
129
|
+
table_definition.send(:integer, id_key)
|
130
|
+
if association.options[:polymorphic]
|
131
|
+
table_definition.send(:string, type_key)
|
132
|
+
add_index [id_key, type_key]
|
133
|
+
else
|
134
|
+
add_index id_key
|
135
|
+
end
|
136
|
+
when :has_and_belongs_to_many
|
137
|
+
table = [table_name, association.name.to_s].sort.join("_")
|
138
|
+
index = ""
|
139
|
+
unless connection.tables.include?(table)
|
140
|
+
connection.create_table(table)
|
141
|
+
connection.add_column table, "#{table.singularize}_id", :integer
|
142
|
+
connection.add_column table, "#{association.name.to_s.singularize}_id", :integer
|
143
|
+
connection.add_index table.to_sym, ["#{table.singularize}_id", "#{association.name.to_s.singularize}_id"].sort.map(&:to_sym), association.options
|
144
|
+
end
|
145
|
+
# Add join table to our schema tables
|
146
|
+
schema_tables << table unless schema_tables.include?(table)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
105
150
|
|
151
|
+
# Grab new schema
|
152
|
+
fields_in_schema = table_definition.columns.inject({}) do |hash, column|
|
153
|
+
hash[column.name.to_s] = column
|
154
|
+
hash
|
155
|
+
end
|
106
156
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
157
|
+
# Add to schema inheritance column if necessary
|
158
|
+
if descendants.present? && !fields_in_schema.include?(inheritance_column.to_s)
|
159
|
+
table_definition.column inheritance_column, :string
|
160
|
+
end
|
112
161
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
options[:null] = column.null if !column.null.nil?
|
119
|
-
connection.add_column table_name, column.name, column.type.to_sym, options
|
120
|
-
end
|
162
|
+
# Remove fields from db no longer in schema
|
163
|
+
(fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
|
164
|
+
column = fields_in_db[field]
|
165
|
+
connection.remove_column table_name, column.name
|
166
|
+
end
|
121
167
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
168
|
+
# Add fields to db new to schema
|
169
|
+
(fields_in_schema.keys - fields_in_db.keys).each do |field|
|
170
|
+
column = fields_in_schema[field]
|
171
|
+
options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
|
172
|
+
options[:default] = column.default if !column.default.nil?
|
173
|
+
options[:null] = column.null if !column.null.nil?
|
174
|
+
connection.add_column table_name, column.name, column.type.to_sym, options
|
175
|
+
end
|
128
176
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
177
|
+
# Change attributes of existent columns
|
178
|
+
(fields_in_schema.keys & fields_in_db.keys).each do |field|
|
179
|
+
if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
|
180
|
+
changed = false # flag
|
181
|
+
new_type = fields_in_schema[field].type.to_sym
|
182
|
+
new_attr = {}
|
133
183
|
|
134
|
-
|
135
|
-
|
136
|
-
new_attr[:precision] = fields_in_schema[field][:precision]
|
137
|
-
new_attr[:scale] = fields_in_schema[field][:scale]
|
138
|
-
|
139
|
-
# Next, iterate through our extended attributes, looking for any differences
|
140
|
-
# This catches stuff like :null, :precision, etc
|
141
|
-
fields_in_schema[field].each_pair do |att,value|
|
142
|
-
next if att == :type or att == :base or att == :name # special cases
|
143
|
-
if !value.nil? && value != fields_in_db[field].send(att)
|
144
|
-
new_attr[att] = value
|
184
|
+
# First, check if the field type changed
|
185
|
+
if fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym
|
145
186
|
changed = true
|
146
187
|
end
|
147
|
-
end
|
148
188
|
|
149
|
-
|
150
|
-
|
189
|
+
# Special catch for precision/scale, since *both* must be specified together
|
190
|
+
# Always include them in the attr struct, but they'll only get applied if changed = true
|
191
|
+
new_attr[:precision] = fields_in_schema[field][:precision]
|
192
|
+
new_attr[:scale] = fields_in_schema[field][:scale]
|
193
|
+
|
194
|
+
# Next, iterate through our extended attributes, looking for any differences
|
195
|
+
# This catches stuff like :null, :precision, etc
|
196
|
+
fields_in_schema[field].each_pair do |att,value|
|
197
|
+
next if att == :type or att == :base or att == :name # special cases
|
198
|
+
if !value.nil? && value != fields_in_db[field].send(att)
|
199
|
+
new_attr[att] = value
|
200
|
+
changed = true
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Change the column if applicable
|
205
|
+
connection.change_column table_name, field, new_type, new_attr if changed
|
206
|
+
end
|
151
207
|
end
|
152
|
-
end
|
153
208
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
209
|
+
# Remove old index
|
210
|
+
# TODO: remove index from habtm t
|
211
|
+
indexes_in_db = connection.indexes(table_name).map(&:name)
|
212
|
+
(indexes_in_db - indexes.keys).each do |name|
|
213
|
+
connection.remove_index(table_name, :name => name)
|
214
|
+
end
|
159
215
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
216
|
+
# Add indexes
|
217
|
+
indexes.each do |name, options|
|
218
|
+
options = options.dup
|
219
|
+
unless connection.indexes(table_name).detect { |i| i.name == name }
|
220
|
+
connection.add_index(table_name, options.delete(:column), options)
|
221
|
+
end
|
165
222
|
end
|
223
|
+
|
224
|
+
# Reload column information
|
225
|
+
reset_column_information
|
166
226
|
end
|
167
227
|
|
168
|
-
# Reload column information
|
169
|
-
reset_column_information
|
170
228
|
end
|
171
229
|
end # ClassMethods
|
172
230
|
end # AutoSchema
|
data/lib/mini_record/version.rb
CHANGED
data/mini_record.gemspec
CHANGED
data/spec/mini_record_spec.rb
CHANGED
@@ -1,26 +1,34 @@
|
|
1
1
|
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
-
require File.expand_path('../models.rb', __FILE__)
|
3
2
|
|
4
3
|
describe MiniRecord do
|
5
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
|
+
|
6
12
|
it 'has #schema inside model' do
|
7
|
-
# For unknown reason separate specs doesn't works
|
8
|
-
ActiveRecord::Base.connection.table_exists?(Person.table_name).must_equal false
|
9
|
-
Person.auto_upgrade!
|
10
13
|
Person.table_name.must_equal 'people'
|
11
|
-
Person.db_columns.sort.must_equal %w[id name]
|
12
|
-
Person.column_names.must_equal Person.db_columns
|
13
|
-
Person.column_names.must_equal Person.schema_columns
|
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
|
14
17
|
person = Person.create(:name => 'foo')
|
15
18
|
person.name.must_equal 'foo'
|
16
19
|
proc { person.surname }.must_raise NoMethodError
|
17
20
|
|
21
|
+
# Test the timestamp columns exist
|
22
|
+
person.must_respond_to :created_at
|
23
|
+
person.must_respond_to :updated_at
|
24
|
+
|
18
25
|
# Add a column without lost data
|
19
26
|
Person.class_eval do
|
20
27
|
schema do |p|
|
21
28
|
p.string :name
|
22
29
|
p.string :surname
|
23
30
|
end
|
31
|
+
timestamps
|
24
32
|
end
|
25
33
|
Person.auto_upgrade!
|
26
34
|
Person.count.must_equal 1
|
@@ -28,22 +36,23 @@ describe MiniRecord do
|
|
28
36
|
person.name.must_equal 'foo'
|
29
37
|
person.surname.must_be_nil
|
30
38
|
person.update_attribute(:surname, 'bar')
|
31
|
-
Person.db_columns.sort.must_equal %w[id name surname]
|
32
|
-
Person.column_names.must_equal Person.db_columns
|
39
|
+
Person.db_columns.sort.must_equal %w[created_at id name surname updated_at]
|
40
|
+
# Person.column_names.must_equal Person.db_columns
|
33
41
|
|
34
42
|
# Remove a column without lost data
|
35
43
|
Person.class_eval do
|
36
44
|
schema do |p|
|
37
45
|
p.string :name
|
38
46
|
end
|
47
|
+
timestamps
|
39
48
|
end
|
40
49
|
Person.auto_upgrade!
|
41
50
|
person = Person.last
|
42
51
|
person.name.must_equal 'foo'
|
43
52
|
proc { person.surname }.must_raise NoMethodError
|
44
|
-
Person.db_columns.sort.must_equal %w[id name]
|
45
|
-
Person.column_names.must_equal Person.db_columns
|
46
|
-
Person.column_names.must_equal Person.schema_columns
|
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
|
47
56
|
|
48
57
|
# Change column without lost data
|
49
58
|
Person.class_eval do
|
@@ -56,9 +65,6 @@ describe MiniRecord do
|
|
56
65
|
end
|
57
66
|
|
58
67
|
it 'has #key,col,property,attribute inside model' do
|
59
|
-
ActiveRecord::Base.connection.table_exists?(Post.table_name).must_equal false
|
60
|
-
ActiveRecord::Base.connection.table_exists?(Category.table_name).must_equal false
|
61
|
-
Post.auto_upgrade!; Category.auto_upgrade!
|
62
68
|
Post.column_names.sort.must_equal Post.db_columns
|
63
69
|
Category.column_names.sort.must_equal Category.schema_columns
|
64
70
|
|
@@ -81,12 +87,11 @@ describe MiniRecord do
|
|
81
87
|
post = Post.first
|
82
88
|
post.name.must_be_nil
|
83
89
|
post.category.must_equal category
|
84
|
-
post.
|
90
|
+
proc { post.title }.must_raise ActiveModel::MissingAttributeError
|
85
91
|
end
|
86
92
|
|
87
93
|
it 'has indexes inside model' do
|
88
94
|
# Check indexes
|
89
|
-
Animal.auto_upgrade!
|
90
95
|
Animal.db_indexes.size.must_be :>, 0
|
91
96
|
Animal.db_indexes.must_equal Animal.indexes.keys.sort
|
92
97
|
|
@@ -110,11 +115,9 @@ describe MiniRecord do
|
|
110
115
|
it 'works with STI' do
|
111
116
|
class Dog < Pet; end
|
112
117
|
class Cat < Pet; end
|
113
|
-
|
118
|
+
ActiveRecord::Base.auto_upgrade!
|
114
119
|
|
115
120
|
# Check inheritance column
|
116
|
-
Pet.db_columns.wont_include "type"
|
117
|
-
Dog.auto_upgrade!
|
118
121
|
Pet.db_columns.must_include "type"
|
119
122
|
|
120
123
|
# Now, let's we know if STI is working
|
@@ -126,7 +129,6 @@ describe MiniRecord do
|
|
126
129
|
Pet.all.map(&:name).must_equal ["foo", "bar"]
|
127
130
|
|
128
131
|
# Check that this doesn't break things
|
129
|
-
Cat.auto_upgrade!
|
130
132
|
Dog.first.name.must_equal "bar"
|
131
133
|
|
132
134
|
# What's happen if we change schema?
|
@@ -135,10 +137,11 @@ describe MiniRecord do
|
|
135
137
|
Dog.class_eval do
|
136
138
|
col :bau
|
137
139
|
end
|
138
|
-
|
140
|
+
ActiveRecord::Base.auto_upgrade!
|
141
|
+
Dog.schema_columns.must_include "bau"
|
139
142
|
Pet.db_columns.must_include "bau"
|
140
|
-
Dog.new.must_respond_to :bau
|
141
|
-
Cat.new.must_respond_to :bau
|
143
|
+
# Dog.new.must_respond_to :bau
|
144
|
+
# Cat.new.must_respond_to :bau
|
142
145
|
end
|
143
146
|
|
144
147
|
it 'works with custom inheritance column' do
|
@@ -146,13 +149,15 @@ describe MiniRecord do
|
|
146
149
|
col :name
|
147
150
|
col :surname
|
148
151
|
col :role
|
149
|
-
|
152
|
+
def self.inheritance_column; 'role'; end
|
150
153
|
end
|
154
|
+
|
151
155
|
class Administrator < User; end
|
152
156
|
class Customer < User; end
|
153
157
|
|
154
158
|
User.auto_upgrade!
|
155
|
-
|
159
|
+
User.inheritance_column.must_equal 'role'
|
160
|
+
Administrator.create(:name => "Davide", :surname => 'DAddYE')
|
156
161
|
Customer.create(:name => "Foo", :surname => "Bar")
|
157
162
|
Administrator.count.must_equal 1
|
158
163
|
Administrator.first.name.must_equal "Davide"
|
@@ -176,4 +181,103 @@ describe MiniRecord do
|
|
176
181
|
fake.category_id.must_equal 1
|
177
182
|
fake.group_id.must_equal 2
|
178
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_purpose_id_and_purposes_tool_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 'should support #belongs_to with :class_name' do
|
256
|
+
Task.schema_columns.must_include 'author_id'
|
257
|
+
Task.db_columns.must_include 'author_id'
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'should support #belongs_to with :foreign_key' do
|
261
|
+
Activity.schema_columns.must_include 'custom_id'
|
262
|
+
Activity.db_columns.must_include 'custom_id'
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'should memonize in schema relationships' do
|
266
|
+
conn = ActiveRecord::Base.connection
|
267
|
+
conn.create_table('foos')
|
268
|
+
conn.add_column :foos, :name, :string
|
269
|
+
conn.add_column :foos, :bar_id, :integer
|
270
|
+
conn.add_index :foos, :bar_id
|
271
|
+
class Foo < ActiveRecord::Base
|
272
|
+
col :name
|
273
|
+
belongs_to :bar
|
274
|
+
end
|
275
|
+
Foo.db_columns.must_include 'name'
|
276
|
+
Foo.db_columns.must_include 'bar_id'
|
277
|
+
Foo.db_indexes.must_include 'index_foos_on_bar_id'
|
278
|
+
Foo.auto_upgrade!
|
279
|
+
Foo.schema_columns.must_include 'name'
|
280
|
+
Foo.schema_columns.must_include 'bar_id'
|
281
|
+
Foo.indexes.must_include 'index_foos_on_bar_id'
|
282
|
+
end
|
179
283
|
end
|
data/spec/models.rb
CHANGED
@@ -1,38 +1,14 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
1
|
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
4
2
|
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
|
5
3
|
|
6
|
-
module SpecHelper
|
7
|
-
def self.included(base)
|
8
|
-
base.extend(ClassMethods)
|
9
|
-
end
|
10
|
-
|
11
|
-
module ClassMethods
|
12
|
-
def db_columns
|
13
|
-
connection.columns(table_name).map(&:name).sort
|
14
|
-
end
|
15
|
-
|
16
|
-
def db_indexes
|
17
|
-
connection.indexes(table_name).map(&:name).sort
|
18
|
-
end
|
19
|
-
|
20
|
-
def schema_columns
|
21
|
-
table_definition.columns.map { |c| c.name.to_s }.sort
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
4
|
class Person < ActiveRecord::Base
|
27
|
-
include SpecHelper
|
28
5
|
schema do |s|
|
29
6
|
s.string :name
|
30
7
|
end
|
8
|
+
timestamps
|
31
9
|
end
|
32
10
|
|
33
11
|
class Post < ActiveRecord::Base
|
34
|
-
include SpecHelper
|
35
|
-
|
36
12
|
key :title
|
37
13
|
key :body
|
38
14
|
key :category, :as => :references
|
@@ -40,21 +16,52 @@ class Post < ActiveRecord::Base
|
|
40
16
|
end
|
41
17
|
|
42
18
|
class Category < ActiveRecord::Base
|
43
|
-
include SpecHelper
|
44
|
-
|
45
19
|
key :title
|
20
|
+
has_many :articles
|
46
21
|
has_many :posts
|
22
|
+
has_many :items
|
47
23
|
end
|
48
24
|
|
49
25
|
class Animal < ActiveRecord::Base
|
50
|
-
include SpecHelper
|
51
|
-
|
52
26
|
key :name, :index => true
|
53
27
|
index :id
|
54
28
|
end
|
55
29
|
|
56
30
|
class Pet < ActiveRecord::Base
|
57
|
-
include SpecHelper
|
58
|
-
|
59
31
|
key :name, :index => true
|
60
32
|
end
|
33
|
+
|
34
|
+
class Tool < ActiveRecord::Base
|
35
|
+
has_and_belongs_to_many :purposes
|
36
|
+
end
|
37
|
+
|
38
|
+
class Purpose < ActiveRecord::Base
|
39
|
+
has_and_belongs_to_many :tools
|
40
|
+
end
|
41
|
+
|
42
|
+
class Publisher < ActiveRecord::Base
|
43
|
+
has_many :articles
|
44
|
+
col :name
|
45
|
+
end
|
46
|
+
|
47
|
+
class Article < ActiveRecord::Base
|
48
|
+
key :title
|
49
|
+
belongs_to :publisher
|
50
|
+
end
|
51
|
+
|
52
|
+
class Attachment < ActiveRecord::Base
|
53
|
+
key :name
|
54
|
+
belongs_to :attachable, :polymorphic => true
|
55
|
+
end
|
56
|
+
|
57
|
+
class Account < ActiveRecord::Base
|
58
|
+
key :name
|
59
|
+
end
|
60
|
+
|
61
|
+
class Task < ActiveRecord::Base
|
62
|
+
belongs_to :author, :class_name => 'Account'
|
63
|
+
end
|
64
|
+
|
65
|
+
class Activity < ActiveRecord::Base
|
66
|
+
belongs_to :author, :class_name => 'Account', :foreign_key => 'custom_id'
|
67
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,3 +2,25 @@ require 'rubygems' unless defined?(Gem)
|
|
2
2
|
require 'bundler/setup'
|
3
3
|
require 'mini_record'
|
4
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)
|
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mini_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 50
|
5
|
+
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
- a
|
11
|
+
version: 0.3.0.a
|
11
12
|
platform: ruby
|
12
13
|
authors:
|
13
14
|
- Davide D'Agostino
|
@@ -15,8 +16,7 @@ autorequire:
|
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date:
|
19
|
-
default_executable:
|
19
|
+
date: 2012-01-21 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: activerecord
|
@@ -26,12 +26,12 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 15
|
30
30
|
segments:
|
31
31
|
- 3
|
32
|
-
-
|
32
|
+
- 2
|
33
33
|
- 0
|
34
|
-
version: 3.
|
34
|
+
version: 3.2.0
|
35
35
|
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
description: "\n\
|
@@ -48,6 +48,7 @@ extra_rdoc_files: []
|
|
48
48
|
|
49
49
|
files:
|
50
50
|
- .gitignore
|
51
|
+
- .travis.yml
|
51
52
|
- Gemfile
|
52
53
|
- README.md
|
53
54
|
- Rakefile
|
@@ -58,7 +59,6 @@ files:
|
|
58
59
|
- spec/mini_record_spec.rb
|
59
60
|
- spec/models.rb
|
60
61
|
- spec/spec_helper.rb
|
61
|
-
has_rdoc: true
|
62
62
|
homepage: https://github.com/DAddYE/mini_record
|
63
63
|
licenses: []
|
64
64
|
|
@@ -79,16 +79,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
79
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
80
|
none: false
|
81
81
|
requirements:
|
82
|
-
- - "
|
82
|
+
- - ">"
|
83
83
|
- !ruby/object:Gem::Version
|
84
|
-
hash:
|
84
|
+
hash: 25
|
85
85
|
segments:
|
86
|
-
-
|
87
|
-
|
86
|
+
- 1
|
87
|
+
- 3
|
88
|
+
- 1
|
89
|
+
version: 1.3.1
|
88
90
|
requirements: []
|
89
91
|
|
90
92
|
rubyforge_project: mini_record
|
91
|
-
rubygems_version: 1.
|
93
|
+
rubygems_version: 1.8.15
|
92
94
|
signing_key:
|
93
95
|
specification_version: 3
|
94
96
|
summary: MiniRecord is a micro gem that allow you to write schema inside your model as you can do in DataMapper.
|
@@ -96,3 +98,4 @@ test_files:
|
|
96
98
|
- spec/mini_record_spec.rb
|
97
99
|
- spec/models.rb
|
98
100
|
- spec/spec_helper.rb
|
101
|
+
has_rdoc:
|