mini_record-compat 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.travis.yml +10 -0
- data/Gemfile +11 -0
- data/README.md +167 -0
- data/Rakefile +38 -0
- data/lib/mini_record/auto_schema.rb +220 -0
- data/lib/mini_record/version.rb +3 -0
- data/lib/mini_record.rb +5 -0
- data/mini_record.gemspec +28 -0
- data/spec/models.rb +70 -0
- data/spec/mysql_spec.rb +31 -0
- data/spec/postgresql_spec.rb +25 -0
- data/spec/shared_examples.rb +220 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/sqlite3_spec.rb +10 -0
- metadata +83 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
[![Build Status](https://secure.travis-ci.org/DAddYE/mini_record.png)](http://travis-ci.org/DAddYE/mini_record)
|
2
|
+
|
3
|
+
|
4
|
+
MiniRecord is a micro extension for our `ActiveRecord` gem.
|
5
|
+
With MiniRecord you can add the ability to create columns outside the default `schema.rb`, directly
|
6
|
+
in your **model** in a similar way that should know in others projects
|
7
|
+
like DataMapper, MongoMapper or MongoID.
|
8
|
+
|
9
|
+
My inspiration come from this handy [project](https://github.com/pjhyett/auto_migrations).
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
* Define columns/properties inside your model
|
14
|
+
* Perform migrations automatically
|
15
|
+
* Auto upgrade your schema, so if you know what you are doing you don't lost your existing data!
|
16
|
+
* Add, Remove, Change Columns; Add, Remove, Change indexes
|
17
|
+
|
18
|
+
## Instructions
|
19
|
+
|
20
|
+
What you need is to move/remove your `db/schema.rb`.
|
21
|
+
This avoid conflicts.
|
22
|
+
|
23
|
+
Add to your `Gemfile`:
|
24
|
+
|
25
|
+
``` rb
|
26
|
+
gem 'mini_record'
|
27
|
+
```
|
28
|
+
|
29
|
+
That's all!
|
30
|
+
|
31
|
+
## Examples
|
32
|
+
|
33
|
+
Remember that inside properties you can use all migrations methods,
|
34
|
+
see [documentation](http://api.rubyonrails.org/classes/ActiveRecord/Migration.html)
|
35
|
+
|
36
|
+
``` rb
|
37
|
+
class Post < ActiveRecord::Base
|
38
|
+
col :title_en, :title_jp
|
39
|
+
col :description_en, :description_jp, :as => :text
|
40
|
+
col :permalink, :index => true, :limit => 50
|
41
|
+
col :comments_count, :as => :integer
|
42
|
+
col :category, :as => :references, :index => true
|
43
|
+
end
|
44
|
+
Post.auto_upgrade!
|
45
|
+
```
|
46
|
+
|
47
|
+
If you don't like `col` there are also few aliases: `key, field, property, attribute`
|
48
|
+
|
49
|
+
Instead of `:as => :my_type` you can use `:type => :my_type`
|
50
|
+
|
51
|
+
Option `:as` or `:type` if not provided is `:string` by default, you can use all ActiveRecord types:
|
52
|
+
|
53
|
+
``` rb
|
54
|
+
:primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean
|
55
|
+
:references, :belongs_to, :polymorphic, :timestamp
|
56
|
+
```
|
57
|
+
|
58
|
+
You can provide others ActiveRecord options like:
|
59
|
+
|
60
|
+
``` rb
|
61
|
+
:limit, :default, :null, :precision, :scale
|
62
|
+
|
63
|
+
# example
|
64
|
+
class Foo < ActiveRecord::Base
|
65
|
+
col :title, :default => "MyTitle" # :as => :string is by default
|
66
|
+
col :price, :as => :decimal, :scale => 8, :precision => 2
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
See [ActiveRecord::TableDefinition](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html)
|
71
|
+
for more details.
|
72
|
+
|
73
|
+
Finally, when you execute `MyModel.auto_upgrade!`, missing columns, indexes and tables will be created on the fly.
|
74
|
+
Indexes and columns present in the db but **not** in your model schema will be **deleted*** also in your db.
|
75
|
+
|
76
|
+
### Single Table Inheritance
|
77
|
+
|
78
|
+
MiniRecord as ActiveRecord support STI plus some goodness, see our specs for more details.
|
79
|
+
|
80
|
+
### Adding a new column
|
81
|
+
|
82
|
+
Super easy, open your model and just add it:
|
83
|
+
|
84
|
+
``` rb
|
85
|
+
class Post < ActiveRecord::Base
|
86
|
+
col :title
|
87
|
+
col :body, :as => :text # <<- this
|
88
|
+
col :permalink, :index => true
|
89
|
+
col :comments_count, :as => :integer
|
90
|
+
col :category, :as => :references, :index => true
|
91
|
+
end
|
92
|
+
Post.auto_upgrade!
|
93
|
+
```
|
94
|
+
|
95
|
+
So now when you invoke `MyModel.auto_upgrade!` you should see a SQL query like `ALTER TABLE` that mean that your existing
|
96
|
+
records are happy and safe.
|
97
|
+
|
98
|
+
### Removing a column
|
99
|
+
|
100
|
+
It's exactly the same, but the column will be _really_ deleted without affect other columns.
|
101
|
+
|
102
|
+
### Change columns
|
103
|
+
|
104
|
+
It's not possible for us know when/what column you have renamed, but we can know if you changed the `type` so
|
105
|
+
if you change `t.string :name` to `t.text :name` we are be able to perform an `ALTER TABLE`
|
106
|
+
|
107
|
+
### Add/Remove indexes
|
108
|
+
|
109
|
+
In the same ways we manage columns MiniRecord will detect new indexes and indexes that needs to be removed.
|
110
|
+
So when you perform `MyModel.auto_upgrade!` a SQL command like:
|
111
|
+
|
112
|
+
``` SQL
|
113
|
+
PRAGMA index_info('index_people_on_name')
|
114
|
+
CREATE INDEX "index_people_on_surname" ON "people" ("surname")
|
115
|
+
```
|
116
|
+
|
117
|
+
Note that writing it in DSL way you have same options as `add_index` so you are be able to write:
|
118
|
+
|
119
|
+
``` rb
|
120
|
+
class Fox < ActiveRecord::Base
|
121
|
+
col :foo, :index => true
|
122
|
+
col :foo, :index => :custom_name
|
123
|
+
col :foo, :index => [:foo, :bar]
|
124
|
+
col :foo, :index => { :column => [:branch_id, :party_id], :unique => true, :name => 'by_branch_party' }
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
That is the same of:
|
129
|
+
|
130
|
+
``` rb
|
131
|
+
class Fox < ActiveRecord::Base
|
132
|
+
col :foo
|
133
|
+
add_index :foo
|
134
|
+
add_index :custom_name
|
135
|
+
add_index [:foo, :bar]
|
136
|
+
add_index [:branch_id, :party_id], :unique => true, :name => 'by_branch_party'
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
## Warnings
|
141
|
+
|
142
|
+
This software is not super well tested in a production project.
|
143
|
+
Im stated to using it in two customer's projects without any problem.
|
144
|
+
|
145
|
+
## TODO
|
146
|
+
|
147
|
+
* reduce the number of methods we add to ActiveRecord::Base objects
|
148
|
+
|
149
|
+
## Author
|
150
|
+
|
151
|
+
DAddYE, you can follow me on twitter [@daddye](http://twitter.com/daddye) or take a look at my site [daddye.it](http://www.daddye.it)
|
152
|
+
|
153
|
+
## Copyright
|
154
|
+
|
155
|
+
Copyright (C) 2011 Davide D'Agostino - [@daddye](http://twitter.com/daddye)
|
156
|
+
|
157
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
158
|
+
associated documentation files (the “Software”), to deal in the Software without restriction, including without
|
159
|
+
limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
160
|
+
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
161
|
+
|
162
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
163
|
+
|
164
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
165
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM,
|
166
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
167
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake"
|
3
|
+
require "rake/testtask"
|
4
|
+
|
5
|
+
%w(install release).each do |task|
|
6
|
+
Rake::Task[task].enhance do
|
7
|
+
sh "rm -rf pkg"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Bump version on github"
|
12
|
+
task :bump do
|
13
|
+
if `git status -s`.strip == ""
|
14
|
+
puts "\e[31mNothing to commit (working directory clean)\e[0m"
|
15
|
+
else
|
16
|
+
version = Bundler.load_gemspec(Dir[File.expand_path('../*.gemspec', __FILE__)].first).version
|
17
|
+
sh "git add .; git commit -a -m \"Bump to version #{version}\""
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
task :release => :bump
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'spec'
|
24
|
+
test.test_files = Dir['spec/**/*_spec.rb']
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
task :test_each_db_adapter do
|
29
|
+
%w{ mysql sqlite3 postgresql }.each do |db_adapter|
|
30
|
+
puts
|
31
|
+
puts "#{'*'*10} Running #{db_adapter} tests"
|
32
|
+
puts
|
33
|
+
puts `bundle exec rake test TEST=spec/#{db_adapter}_spec.rb`
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :test_each_db_adapter
|
38
|
+
task :spec => :test_each_db_adapter
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
module MiniRecord
|
3
|
+
module AutoSchema
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def table_definition
|
11
|
+
return superclass.table_definition unless superclass == ActiveRecord::Base
|
12
|
+
|
13
|
+
@_table_definition ||= begin
|
14
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def indexes
|
19
|
+
return superclass.indexes unless superclass == ActiveRecord::Base
|
20
|
+
|
21
|
+
@_indexes ||= {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def col(*args)
|
25
|
+
return unless connection?
|
26
|
+
|
27
|
+
options = args.extract_options!
|
28
|
+
type = options.delete(:as) || options.delete(:type) || :string
|
29
|
+
args.each do |column_name|
|
30
|
+
table_definition.send(type, column_name, options)
|
31
|
+
column_name = table_definition.columns[-1].name
|
32
|
+
case index_name = options.delete(:index)
|
33
|
+
when Hash
|
34
|
+
add_index(options.delete(:column) || column_name, index_name)
|
35
|
+
when TrueClass
|
36
|
+
add_index(column_name)
|
37
|
+
when String, Symbol, Array
|
38
|
+
add_index(index_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
alias :key :col
|
43
|
+
alias :property :col
|
44
|
+
alias :field :col
|
45
|
+
alias :attribute :col
|
46
|
+
|
47
|
+
def reset_table_definition!
|
48
|
+
@_table_definition = nil
|
49
|
+
end
|
50
|
+
alias :reset_schema! :reset_table_definition!
|
51
|
+
|
52
|
+
def schema
|
53
|
+
reset_table_definition!
|
54
|
+
yield table_definition
|
55
|
+
table_definition
|
56
|
+
end
|
57
|
+
alias :keys :schema
|
58
|
+
alias :properties :schema
|
59
|
+
alias :fields :schema
|
60
|
+
alias :attributes :schema
|
61
|
+
|
62
|
+
def add_index(column_name, options={})
|
63
|
+
index_name = shorten_index_name connection.index_name(table_name, :column => column_name)
|
64
|
+
indexes[index_name] = options.merge(:column => column_name, :name => index_name)
|
65
|
+
index_name
|
66
|
+
end
|
67
|
+
alias :index :add_index
|
68
|
+
|
69
|
+
def connection?
|
70
|
+
!!connection
|
71
|
+
rescue Exception => e
|
72
|
+
puts "\e[31m%s\e[0m" % e.message.strip
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
def shorten_index_name(name)
|
77
|
+
if name.length < connection.index_name_length
|
78
|
+
name
|
79
|
+
else
|
80
|
+
name[0..(connection.index_name_length-11)] + ::Zlib.crc32(name).to_s
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def sqlite?
|
85
|
+
connection.adapter_name =~ /sqlite/i
|
86
|
+
end
|
87
|
+
|
88
|
+
def mysql?
|
89
|
+
connection.adapter_name =~ /mysql/i
|
90
|
+
end
|
91
|
+
|
92
|
+
def postgresql?
|
93
|
+
connection.adapter_name =~ /postgresql/i
|
94
|
+
end
|
95
|
+
|
96
|
+
def auto_upgrade!(create_table_options = '')
|
97
|
+
return unless connection?
|
98
|
+
|
99
|
+
# normally activerecord's mysql adapter does this
|
100
|
+
if mysql?
|
101
|
+
create_table_options ||= 'ENGINE=InnoDB'
|
102
|
+
end
|
103
|
+
|
104
|
+
non_standard_primary_key = if (primary_key_column = table_definition.columns.detect { |column| column.name.to_s == primary_key.to_s })
|
105
|
+
primary_key_column.type != :primary_key
|
106
|
+
end
|
107
|
+
|
108
|
+
unless non_standard_primary_key
|
109
|
+
table_definition.column :id, :primary_key
|
110
|
+
end
|
111
|
+
|
112
|
+
# Table doesn't exist, create it
|
113
|
+
unless connection.table_exists? table_name
|
114
|
+
|
115
|
+
# avoid using connection.create_table because in 3.0.x it ignores table_definition
|
116
|
+
# and it also is too eager about adding a primary key column
|
117
|
+
create_sql = "CREATE TABLE "
|
118
|
+
create_sql << "#{quoted_table_name} ("
|
119
|
+
create_sql << table_definition.to_sql
|
120
|
+
create_sql << ") #{create_table_options}"
|
121
|
+
connection.execute create_sql
|
122
|
+
|
123
|
+
if non_standard_primary_key
|
124
|
+
if sqlite?
|
125
|
+
add_index primary_key, :unique => true
|
126
|
+
elsif mysql? or postgresql?
|
127
|
+
# can't use add_index method because it won't let you do "PRIMARY KEY"
|
128
|
+
connection.execute "ALTER TABLE #{quoted_table_name} ADD PRIMARY KEY (#{quoted_primary_key})"
|
129
|
+
else
|
130
|
+
raise RuntimeError, "mini_record doesn't support non-standard primary keys for the #{connection.adapter_name} adapter!"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
reset_column_information
|
135
|
+
end
|
136
|
+
|
137
|
+
# Add to schema inheritance column if necessary
|
138
|
+
if descendants.present? && !table_definition.columns.any? { |column| column.name.to_s == inheritance_column.to_s }
|
139
|
+
table_definition.column inheritance_column, :string
|
140
|
+
end
|
141
|
+
|
142
|
+
# Grab database columns
|
143
|
+
fields_in_db = connection.columns(table_name).inject({}) do |hash, column|
|
144
|
+
hash[column.name] = column
|
145
|
+
hash
|
146
|
+
end
|
147
|
+
|
148
|
+
# Grab new schema
|
149
|
+
fields_in_schema = table_definition.columns.inject({}) do |hash, column|
|
150
|
+
hash[column.name.to_s] = column
|
151
|
+
hash
|
152
|
+
end
|
153
|
+
|
154
|
+
# Remove fields from db no longer in schema
|
155
|
+
(fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
|
156
|
+
column = fields_in_db[field]
|
157
|
+
connection.remove_column table_name, column.name
|
158
|
+
end
|
159
|
+
|
160
|
+
# Add fields to db new to schema
|
161
|
+
(fields_in_schema.keys - fields_in_db.keys).each do |field|
|
162
|
+
column = fields_in_schema[field]
|
163
|
+
options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
|
164
|
+
options[:default] = column.default if !column.default.nil?
|
165
|
+
options[:null] = column.null if !column.null.nil?
|
166
|
+
connection.add_column table_name, column.name, column.type.to_sym, options
|
167
|
+
end
|
168
|
+
|
169
|
+
# Change attributes of existent columns
|
170
|
+
(fields_in_schema.keys & fields_in_db.keys).each do |field|
|
171
|
+
if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
|
172
|
+
changed = false # flag
|
173
|
+
new_type = fields_in_schema[field].type.to_sym
|
174
|
+
new_attr = {}
|
175
|
+
|
176
|
+
# First, check if the field type changed
|
177
|
+
if fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym
|
178
|
+
changed = true
|
179
|
+
end
|
180
|
+
|
181
|
+
# Special catch for precision/scale, since *both* must be specified together
|
182
|
+
# Always include them in the attr struct, but they'll only get applied if changed = true
|
183
|
+
new_attr[:precision] = fields_in_schema[field][:precision]
|
184
|
+
new_attr[:scale] = fields_in_schema[field][:scale]
|
185
|
+
|
186
|
+
# Next, iterate through our extended attributes, looking for any differences
|
187
|
+
# This catches stuff like :null, :precision, etc
|
188
|
+
fields_in_schema[field].each_pair do |att,value|
|
189
|
+
next if att == :type or att == :base or att == :name # special cases
|
190
|
+
if !value.nil? && value != fields_in_db[field].send(att)
|
191
|
+
new_attr[att] = value
|
192
|
+
changed = true
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Change the column if applicable
|
197
|
+
connection.change_column table_name, field, new_type, new_attr if changed
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Remove old index
|
202
|
+
indexes_in_db = connection.indexes(table_name).map(&:name)
|
203
|
+
(indexes_in_db - indexes.keys).each do |name|
|
204
|
+
connection.remove_index(table_name, :name => name)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Add indexes
|
208
|
+
indexes.each do |name, options|
|
209
|
+
options = options.dup
|
210
|
+
unless connection.indexes(table_name).detect { |i| i.name == name }
|
211
|
+
connection.add_index(table_name, options.delete(:column), options)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Reload column information
|
216
|
+
reset_column_information
|
217
|
+
end
|
218
|
+
end # ClassMethods
|
219
|
+
end # AutoSchema
|
220
|
+
end # MiniRecord
|
data/lib/mini_record.rb
ADDED
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-compat"
|
7
|
+
s.version = MiniRecord::VERSION
|
8
|
+
s.authors = ["Davide D'Agostino", "Seamus Abshere"]
|
9
|
+
s.email = ["d.dagostino@lipsiasoft.com", "seamus@abshere.net"]
|
10
|
+
s.homepage = "https://github.com/seamusabshere/mini_record"
|
11
|
+
s.summary = %q{Alternate gem published by Seamus Abshere for ActiveRecord 3.0 support. 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
|
+
s.add_runtime_dependency "activerecord", ">=3"
|
26
|
+
|
27
|
+
# dev dependencies appear to be in the Gemfile
|
28
|
+
end
|
data/spec/models.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# be sure to set up activerecord before you require this helper
|
2
|
+
|
3
|
+
class Person < ActiveRecord::Base
|
4
|
+
include SpecHelper
|
5
|
+
schema do |s|
|
6
|
+
s.string :name
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Post < ActiveRecord::Base
|
11
|
+
include SpecHelper
|
12
|
+
|
13
|
+
key :title
|
14
|
+
key :body
|
15
|
+
key :category, :as => :references
|
16
|
+
belongs_to :category
|
17
|
+
end
|
18
|
+
|
19
|
+
class Category < ActiveRecord::Base
|
20
|
+
include SpecHelper
|
21
|
+
|
22
|
+
key :title
|
23
|
+
has_many :posts
|
24
|
+
end
|
25
|
+
|
26
|
+
class Animal < ActiveRecord::Base
|
27
|
+
include SpecHelper
|
28
|
+
|
29
|
+
key :name, :index => true
|
30
|
+
index :id
|
31
|
+
end
|
32
|
+
|
33
|
+
class Pet < ActiveRecord::Base
|
34
|
+
include SpecHelper
|
35
|
+
|
36
|
+
key :name, :index => true
|
37
|
+
end
|
38
|
+
class Dog < Pet; end
|
39
|
+
class Cat < Pet; end
|
40
|
+
|
41
|
+
class Vegetable < ActiveRecord::Base
|
42
|
+
include SpecHelper
|
43
|
+
|
44
|
+
set_primary_key :latin_name
|
45
|
+
|
46
|
+
col :latin_name
|
47
|
+
col :common_name
|
48
|
+
end
|
49
|
+
|
50
|
+
class User < ActiveRecord::Base
|
51
|
+
include SpecHelper
|
52
|
+
col :name
|
53
|
+
col :surname
|
54
|
+
col :role
|
55
|
+
set_inheritance_column :role
|
56
|
+
end
|
57
|
+
class Administrator < User; end
|
58
|
+
class Customer < User; end
|
59
|
+
|
60
|
+
class Fake < ActiveRecord::Base
|
61
|
+
include SpecHelper
|
62
|
+
col :name, :surname
|
63
|
+
col :category, :group, :as => :references
|
64
|
+
end
|
65
|
+
|
66
|
+
class AutomobileMakeModelYearVariant < ActiveRecord::Base
|
67
|
+
include SpecHelper
|
68
|
+
col :make_model_year_name
|
69
|
+
add_index :make_model_year_name
|
70
|
+
end
|
data/spec/mysql_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
bin = ENV['TEST_MYSQL_BIN'] || 'mysql'
|
4
|
+
username = ENV['TEST_MYSQL_USERNAME'] || 'root'
|
5
|
+
password = ENV['TEST_MYSQL_PASSWORD'] || 'password'
|
6
|
+
database = ENV['TEST_MYSQL_DATABASE'] || 'test_mini_record'
|
7
|
+
cmd = "#{bin} -u #{username} -p#{password}"
|
8
|
+
|
9
|
+
`#{cmd} -e 'show databases'`
|
10
|
+
unless $?.success?
|
11
|
+
$stderr.puts "Skipping mysql tests because `#{cmd}` doesn't work"
|
12
|
+
exit 0
|
13
|
+
end
|
14
|
+
|
15
|
+
system %{#{cmd} -e "drop database #{database}"}
|
16
|
+
system %{#{cmd} -e "create database #{database}"}
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection(
|
19
|
+
'adapter' => 'mysql',
|
20
|
+
'encoding' => 'utf8',
|
21
|
+
'database' => database,
|
22
|
+
'username' => username,
|
23
|
+
'password' => password
|
24
|
+
)
|
25
|
+
|
26
|
+
# require 'logger'
|
27
|
+
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
|
28
|
+
|
29
|
+
require File.expand_path('../models.rb', __FILE__)
|
30
|
+
|
31
|
+
require File.expand_path('../shared_examples.rb', __FILE__)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
createdb_bin = ENV['TEST_CREATEDB_BIN'] || 'createdb'
|
4
|
+
dropdb_bin = ENV['TEST_DROPDB_BIN'] || 'dropdb'
|
5
|
+
username = ENV['TEST_POSTGRES_USERNAME'] || `whoami`.chomp
|
6
|
+
# password = ENV['TEST_POSTGRES_PASSWORD'] || 'password'
|
7
|
+
database = ENV['TEST_POSTGRES_DATABASE'] || 'test_mini_record'
|
8
|
+
|
9
|
+
system %{#{dropdb_bin} #{database}}
|
10
|
+
system %{#{createdb_bin} #{database}}
|
11
|
+
|
12
|
+
ActiveRecord::Base.establish_connection(
|
13
|
+
'adapter' => 'postgresql',
|
14
|
+
'encoding' => 'utf8',
|
15
|
+
'database' => database,
|
16
|
+
'username' => username,
|
17
|
+
# 'password' => password
|
18
|
+
)
|
19
|
+
|
20
|
+
# require 'logger'
|
21
|
+
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
|
22
|
+
|
23
|
+
require File.expand_path('../models.rb', __FILE__)
|
24
|
+
|
25
|
+
require File.expand_path('../shared_examples.rb', __FILE__)
|
@@ -0,0 +1,220 @@
|
|
1
|
+
describe MiniRecord do
|
2
|
+
before do
|
3
|
+
ActiveRecord::Base.descendants.each do |active_record|
|
4
|
+
ActiveRecord::Base.connection.drop_table active_record.table_name if active_record.table_exists?
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'has #schema inside model' do
|
9
|
+
# For unknown reason separate specs doesn't works
|
10
|
+
ActiveRecord::Base.connection.table_exists?(Person.table_name).must_equal false
|
11
|
+
Person.auto_upgrade!
|
12
|
+
Person.table_name.must_equal 'people'
|
13
|
+
Person.db_columns.sort.must_equal %w[id name]
|
14
|
+
Person.column_names.sort.must_equal Person.db_columns
|
15
|
+
Person.column_names.sort.must_equal Person.schema_columns
|
16
|
+
person = Person.create(:name => 'foo')
|
17
|
+
person.name.must_equal 'foo'
|
18
|
+
proc { person.surname }.must_raise NoMethodError
|
19
|
+
|
20
|
+
# Add a column without lost data
|
21
|
+
Person.class_eval do
|
22
|
+
schema do |p|
|
23
|
+
p.string :name
|
24
|
+
p.string :surname
|
25
|
+
end
|
26
|
+
end
|
27
|
+
Person.auto_upgrade!
|
28
|
+
Person.count.must_equal 1
|
29
|
+
person = Person.last
|
30
|
+
person.name.must_equal 'foo'
|
31
|
+
person.surname.must_be_nil
|
32
|
+
person.update_attribute(:surname, 'bar')
|
33
|
+
Person.db_columns.sort.must_equal %w[id name surname]
|
34
|
+
Person.column_names.sort.must_equal Person.db_columns
|
35
|
+
|
36
|
+
# Remove a column without lost data
|
37
|
+
Person.class_eval do
|
38
|
+
schema do |p|
|
39
|
+
p.string :name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
Person.auto_upgrade!
|
43
|
+
person = Person.last
|
44
|
+
person.name.must_equal 'foo'
|
45
|
+
proc { person.surname }.must_raise NoMethodError
|
46
|
+
Person.db_columns.sort.must_equal %w[id name]
|
47
|
+
Person.column_names.sort.must_equal Person.db_columns
|
48
|
+
Person.column_names.sort.must_equal Person.schema_columns
|
49
|
+
|
50
|
+
# Change column without lost data
|
51
|
+
Person.class_eval do
|
52
|
+
schema do |p|
|
53
|
+
p.text :name
|
54
|
+
end
|
55
|
+
end
|
56
|
+
person = Person.last
|
57
|
+
person.name.must_equal 'foo'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'has #key,col,property,attribute inside model' do
|
61
|
+
ActiveRecord::Base.connection.table_exists?(Post.table_name).must_equal false
|
62
|
+
ActiveRecord::Base.connection.table_exists?(Category.table_name).must_equal false
|
63
|
+
Post.auto_upgrade!; Category.auto_upgrade!
|
64
|
+
Post.column_names.sort.must_equal Post.db_columns
|
65
|
+
Category.column_names.sort.must_equal Category.schema_columns
|
66
|
+
|
67
|
+
# Check default properties
|
68
|
+
category = Category.create(:title => 'category')
|
69
|
+
post = Post.create(:title => 'foo', :body => 'bar', :category_id => category.id)
|
70
|
+
post = Post.first
|
71
|
+
post.title.must_equal 'foo'
|
72
|
+
post.body.must_equal 'bar'
|
73
|
+
post.category.must_equal category
|
74
|
+
|
75
|
+
|
76
|
+
# Remove a column
|
77
|
+
Post.reset_table_definition!
|
78
|
+
Post.class_eval do
|
79
|
+
col :name
|
80
|
+
col :category, :as => :references
|
81
|
+
end
|
82
|
+
Post.auto_upgrade!
|
83
|
+
post = Post.first
|
84
|
+
post.name.must_be_nil
|
85
|
+
post.category.must_equal category
|
86
|
+
post.wont_respond_to :title
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'has indexes inside model' do
|
90
|
+
# Check indexes
|
91
|
+
Animal.auto_upgrade!
|
92
|
+
Animal.db_indexes.size.must_be :>, 0
|
93
|
+
Animal.db_indexes.must_equal Animal.indexes.keys.sort
|
94
|
+
|
95
|
+
indexes_was = Animal.db_indexes
|
96
|
+
|
97
|
+
# Remove an index
|
98
|
+
Animal.indexes.delete(indexes_was.pop)
|
99
|
+
Animal.auto_upgrade!
|
100
|
+
Animal.indexes.keys.sort.must_equal indexes_was
|
101
|
+
Animal.db_indexes.must_equal indexes_was
|
102
|
+
|
103
|
+
# Add a new index
|
104
|
+
Animal.class_eval do
|
105
|
+
col :category, :as => :references, :index => true
|
106
|
+
end
|
107
|
+
Animal.auto_upgrade!
|
108
|
+
Animal.db_columns.must_include "category_id"
|
109
|
+
Animal.db_indexes.must_equal((indexes_was << "index_animals_on_category_id").sort)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'works with STI' do
|
113
|
+
Pet.auto_upgrade!
|
114
|
+
Pet.reset_column_information
|
115
|
+
Pet.db_columns.must_include "type"
|
116
|
+
Dog.auto_upgrade!
|
117
|
+
Pet.db_columns.must_include "type"
|
118
|
+
|
119
|
+
# Now, let's we know if STI is working
|
120
|
+
Pet.create(:name => "foo")
|
121
|
+
Dog.create(:name => "bar")
|
122
|
+
Dog.count.must_equal 1
|
123
|
+
Dog.first.name.must_equal "bar"
|
124
|
+
Pet.count.must_equal 2
|
125
|
+
Pet.all.map(&:name).must_equal ["foo", "bar"]
|
126
|
+
|
127
|
+
# Check that this doesn't break things
|
128
|
+
Cat.auto_upgrade!
|
129
|
+
Dog.first.name.must_equal "bar"
|
130
|
+
|
131
|
+
# What's happen if we change schema?
|
132
|
+
Dog.table_definition.must_equal Pet.table_definition
|
133
|
+
Dog.indexes.must_equal Pet.indexes
|
134
|
+
Dog.class_eval do
|
135
|
+
col :bau
|
136
|
+
end
|
137
|
+
Dog.auto_upgrade!
|
138
|
+
Pet.db_columns.must_include "bau"
|
139
|
+
Dog.new.must_respond_to :bau
|
140
|
+
Cat.new.must_respond_to :bau
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'works with custom inheritance column' do
|
144
|
+
User.auto_upgrade!
|
145
|
+
Administrator.create(:name => "Davide", :surname => "D'Agostino")
|
146
|
+
Customer.create(:name => "Foo", :surname => "Bar")
|
147
|
+
Administrator.count.must_equal 1
|
148
|
+
Administrator.first.name.must_equal "Davide"
|
149
|
+
Customer.count.must_equal 1
|
150
|
+
Customer.first.name.must_equal "Foo"
|
151
|
+
User.count.must_equal 2
|
152
|
+
User.first.role.must_equal "Administrator"
|
153
|
+
User.last.role.must_equal "Customer"
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'allow multiple columns definitions' do
|
157
|
+
Fake.auto_upgrade!
|
158
|
+
Fake.create(:name => 'foo', :surname => 'bar', :category_id => 1, :group_id => 2)
|
159
|
+
fake = Fake.first
|
160
|
+
fake.name.must_equal 'foo'
|
161
|
+
fake.surname.must_equal 'bar'
|
162
|
+
fake.category_id.must_equal 1
|
163
|
+
fake.group_id.must_equal 2
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'allows non-integer primary keys' do
|
167
|
+
Vegetable.auto_upgrade!
|
168
|
+
Vegetable.primary_key.must_equal 'latin_name'
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'properly creates primary key columns so that ActiveRecord uses them' do
|
172
|
+
Vegetable.auto_upgrade!
|
173
|
+
Vegetable.delete_all
|
174
|
+
n = 'roobus roobious'
|
175
|
+
v = Vegetable.new; v.latin_name = n; v.save!
|
176
|
+
Vegetable.find(n).must_equal v
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'automatically shortens long index names' do
|
180
|
+
AutomobileMakeModelYearVariant.auto_upgrade!
|
181
|
+
AutomobileMakeModelYearVariant.db_indexes.first.start_with?('index_automobile_make_model_ye').must_equal true
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'properly creates primary key columns that are unique' do
|
185
|
+
Vegetable.auto_upgrade!
|
186
|
+
Vegetable.delete_all
|
187
|
+
n = 'roobus roobious'
|
188
|
+
v = Vegetable.new; v.latin_name = n; v.save!
|
189
|
+
if sqlite?
|
190
|
+
flunk # segfaults
|
191
|
+
# lambda { v = Vegetable.new; v.latin_name = n; v.save! }.must_raise(SQLite3::ConstraintException)
|
192
|
+
else
|
193
|
+
lambda { v = Vegetable.new; v.latin_name = n; v.save! }.must_raise(ActiveRecord::RecordNotUnique)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'is idempotent' do
|
198
|
+
ActiveRecord::Base.descendants.each do |active_record|
|
199
|
+
active_record.auto_upgrade!
|
200
|
+
active_record.reset_column_information
|
201
|
+
before = [ active_record.db_columns, active_record.db_indexes ]
|
202
|
+
active_record.auto_upgrade!
|
203
|
+
active_record.reset_column_information
|
204
|
+
[ active_record.db_columns, active_record.db_indexes ].must_equal before
|
205
|
+
active_record.auto_upgrade!
|
206
|
+
active_record.reset_column_information
|
207
|
+
active_record.auto_upgrade!
|
208
|
+
active_record.reset_column_information
|
209
|
+
active_record.auto_upgrade!
|
210
|
+
active_record.reset_column_information
|
211
|
+
[ active_record.db_columns, active_record.db_indexes ].must_equal before
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
def sqlite?
|
218
|
+
ActiveRecord::Base.connection.adapter_name =~ /sqlite/i
|
219
|
+
end
|
220
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems' unless defined?(Gem)
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'mini_record'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
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
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
4
|
+
|
5
|
+
# require 'logger'
|
6
|
+
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
|
7
|
+
|
8
|
+
require File.expand_path('../models.rb', __FILE__)
|
9
|
+
|
10
|
+
require File.expand_path('../shared_examples.rb', __FILE__)
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mini_record-compat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Davide D'Agostino
|
9
|
+
- Seamus Abshere
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2011-09-26 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activerecord
|
17
|
+
requirement: &2154134460 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2154134460
|
26
|
+
description: ! "\nWith it you can add the ability to create columns outside the default
|
27
|
+
schema, directly\nin your model in a similar way that you just know in others projects\nlike
|
28
|
+
\ DataMapper or MongoMapper.\n "
|
29
|
+
email:
|
30
|
+
- d.dagostino@lipsiasoft.com
|
31
|
+
- seamus@abshere.net
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- .gitignore
|
37
|
+
- .travis.yml
|
38
|
+
- Gemfile
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- lib/mini_record.rb
|
42
|
+
- lib/mini_record/auto_schema.rb
|
43
|
+
- lib/mini_record/version.rb
|
44
|
+
- mini_record.gemspec
|
45
|
+
- spec/models.rb
|
46
|
+
- spec/mysql_spec.rb
|
47
|
+
- spec/postgresql_spec.rb
|
48
|
+
- spec/shared_examples.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
- spec/sqlite3_spec.rb
|
51
|
+
homepage: https://github.com/seamusabshere/mini_record
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.8.6
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Alternate gem published by Seamus Abshere for ActiveRecord 3.0 support. MiniRecord
|
75
|
+
is a micro gem that allow you to write schema inside your model as you can do in
|
76
|
+
DataMapper.
|
77
|
+
test_files:
|
78
|
+
- spec/models.rb
|
79
|
+
- spec/mysql_spec.rb
|
80
|
+
- spec/postgresql_spec.rb
|
81
|
+
- spec/shared_examples.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
- spec/sqlite3_spec.rb
|