mini_record-compat 0.2.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/.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
|
+
[](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
|