modalfields 1.1.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/.document +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +43 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +133 -0
- data/Rakefile +49 -0
- data/TODO +33 -0
- data/VERSION +1 -0
- data/lib/modalfields.rb +13 -0
- data/lib/modalfields/modalfields.rb +428 -0
- data/lib/modalfields/standardfields.rb +13 -0
- data/lib/modalfields/tasks.rb +1 -0
- data/lib/tasks/check.rake +6 -0
- data/lib/tasks/migrate.rake +17 -0
- data/lib/tasks/update.rake +6 -0
- data/test/create_database.rb +47 -0
- data/test/database.yml +11 -0
- data/test/helper.rb +61 -0
- data/test/model_cases/bare/after/author.rb +12 -0
- data/test/model_cases/bare/after/book.rb +12 -0
- data/test/model_cases/bare/before/author.rb +3 -0
- data/test/model_cases/bare/before/book.rb +4 -0
- data/test/model_cases/clean/after/author.rb +14 -0
- data/test/model_cases/clean/after/book.rb +14 -0
- data/test/model_cases/clean/before/author.rb +14 -0
- data/test/model_cases/clean/before/book.rb +14 -0
- data/test/model_cases/dirty/after/author.rb +14 -0
- data/test/model_cases/dirty/after/book.rb +14 -0
- data/test/model_cases/dirty/before/author.rb +15 -0
- data/test/model_cases/dirty/before/book.rb +14 -0
- data/test/schema.rb +17 -0
- data/test/test_diff.rb +127 -0
- data/test/test_update.rb +52 -0
- metadata +228 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
# Define built-in column types, with default values for valid attributes
|
2
|
+
ModalFields.define do
|
3
|
+
string :limit=>255
|
4
|
+
text :limit=>nil
|
5
|
+
integer :limit=>nil
|
6
|
+
float
|
7
|
+
decimal :scale=>nil, :precision=>nil
|
8
|
+
datetime
|
9
|
+
time
|
10
|
+
date
|
11
|
+
binary :limit=>nil
|
12
|
+
boolean
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '..', 'tasks', '**/*.rake')].each { |f| load f }
|
@@ -0,0 +1,17 @@
|
|
1
|
+
namespace :db do
|
2
|
+
task :migrate do
|
3
|
+
ModalFields.update
|
4
|
+
end
|
5
|
+
|
6
|
+
task :update => [:migrate] do
|
7
|
+
ModalFields.update
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :migrate do
|
11
|
+
[:up, :down, :reset, :redo].each do |t|
|
12
|
+
task t do
|
13
|
+
ModalFields.update
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
def create_database(config)
|
2
|
+
begin
|
3
|
+
if config['adapter'] =~ /sqlite/
|
4
|
+
if File.exist?(config['database'])
|
5
|
+
$stderr.puts "#{config['database']} already exists"
|
6
|
+
else
|
7
|
+
begin
|
8
|
+
# Create the SQLite database
|
9
|
+
ActiveRecord::Base.establish_connection(config)
|
10
|
+
ActiveRecord::Base.connection
|
11
|
+
rescue
|
12
|
+
$stderr.puts $!, *($!.backtrace)
|
13
|
+
$stderr.puts "Couldn't create database for #{config.inspect}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
return # Skip the else clause of begin/rescue
|
17
|
+
else
|
18
|
+
ActiveRecord::Base.establish_connection(config)
|
19
|
+
ActiveRecord::Base.connection
|
20
|
+
end
|
21
|
+
rescue
|
22
|
+
case config['adapter']
|
23
|
+
when 'mysql'
|
24
|
+
@charset = ENV['CHARSET'] || 'utf8'
|
25
|
+
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
|
26
|
+
begin
|
27
|
+
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
|
28
|
+
ActiveRecord::Base.connection.create_database(config['database'], :charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation))
|
29
|
+
ActiveRecord::Base.establish_connection(config)
|
30
|
+
rescue
|
31
|
+
$stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation} (if you set the charset manually, make sure you have a matching collation)"
|
32
|
+
end
|
33
|
+
when 'postgresql'
|
34
|
+
@encoding = config[:encoding] || ENV['CHARSET'] || 'utf8'
|
35
|
+
begin
|
36
|
+
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
|
37
|
+
ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
|
38
|
+
ActiveRecord::Base.establish_connection(config)
|
39
|
+
rescue
|
40
|
+
$stderr.puts $!, *($!.backtrace)
|
41
|
+
$stderr.puts "Couldn't create database for #{config.inspect}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
$stderr.puts "#{config['database']} already exists"
|
46
|
+
end
|
47
|
+
end
|
data/test/database.yml
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
require 'active_support'
|
13
|
+
require 'active_record'
|
14
|
+
require 'logger'
|
15
|
+
|
16
|
+
require 'active_support/core_ext/hash'
|
17
|
+
|
18
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
19
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
20
|
+
require 'modalfields'
|
21
|
+
|
22
|
+
class Test::Unit::TestCase
|
23
|
+
end
|
24
|
+
|
25
|
+
ENV['RAILS_ENV'] = 'test'
|
26
|
+
ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) # + '/../../../..'
|
27
|
+
|
28
|
+
# require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
|
29
|
+
|
30
|
+
module Rails
|
31
|
+
def self.root
|
32
|
+
ENV['RAILS_ROOT']
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_schema
|
37
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')).with_indifferent_access
|
38
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
39
|
+
|
40
|
+
db_adapter = ENV['DB']
|
41
|
+
|
42
|
+
# no db passed, try one of these fine config-free DBs before bombing.
|
43
|
+
db_adapter ||=
|
44
|
+
begin
|
45
|
+
require 'rubygems'
|
46
|
+
require 'sqlite3'
|
47
|
+
'sqlite3'
|
48
|
+
rescue MissingSourceFile
|
49
|
+
end
|
50
|
+
|
51
|
+
if db_adapter.nil?
|
52
|
+
raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite3."
|
53
|
+
end
|
54
|
+
|
55
|
+
require File.dirname(__FILE__) + '/create_database'
|
56
|
+
create_database config[db_adapter]
|
57
|
+
|
58
|
+
ActiveRecord::Base.establish_connection(config[db_adapter])
|
59
|
+
load(File.dirname(__FILE__) + "/schema.rb")
|
60
|
+
require File.dirname(__FILE__) + '/../rails/init'
|
61
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Author < ActiveRecord::Base
|
2
|
+
|
3
|
+
fields {
|
4
|
+
# The comments must be prerserved
|
5
|
+
number :integer # as well as the order of field declarations,
|
6
|
+
name :string # indendationa
|
7
|
+
birthdate :date, :unique # specifications...
|
8
|
+
# etc.
|
9
|
+
decnum :decimal, :default=>BigDecimal('1.2'), :precision=>10, :scale=>3
|
10
|
+
event :datetime
|
11
|
+
}
|
12
|
+
|
13
|
+
has_many :books
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Book < ActiveRecord::Base
|
2
|
+
|
3
|
+
# the position of the fields block must be preserved
|
4
|
+
belongs_to :author
|
5
|
+
|
6
|
+
fields do
|
7
|
+
title :string
|
8
|
+
price :decimal, :precision=>8, :scale=>2
|
9
|
+
code :string, :limit=>4
|
10
|
+
comments :text
|
11
|
+
timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Author < ActiveRecord::Base
|
2
|
+
|
3
|
+
fields {
|
4
|
+
# The comments must be prerserved
|
5
|
+
number :integer # as well as the order of field declarations,
|
6
|
+
name :string # indendationa
|
7
|
+
birthdate :date, :unique # specifications...
|
8
|
+
# etc.
|
9
|
+
decnum :decimal, :default=>BigDecimal('1.2'), :precision=>10, :scale=>3
|
10
|
+
event :datetime
|
11
|
+
}
|
12
|
+
|
13
|
+
has_many :books
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Book < ActiveRecord::Base
|
2
|
+
|
3
|
+
# the position of the fields block must be preserved
|
4
|
+
belongs_to :author
|
5
|
+
|
6
|
+
fields do
|
7
|
+
title :string
|
8
|
+
price :decimal, :precision=>8, :scale=>2
|
9
|
+
code :string, :limit=>4
|
10
|
+
comments :text
|
11
|
+
timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Author < ActiveRecord::Base
|
2
|
+
|
3
|
+
fields {
|
4
|
+
# The comments must be prerserved
|
5
|
+
number :integer # as well as the order of field declarations,
|
6
|
+
name :string # indendation
|
7
|
+
birthdate :date, :unique # specifications...
|
8
|
+
# etc.
|
9
|
+
decnum :decimal, :default=>BigDecimal('1.2'), :precision=>10, :scale=>3
|
10
|
+
event :datetime
|
11
|
+
}
|
12
|
+
|
13
|
+
has_many :books
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Book < ActiveRecord::Base
|
2
|
+
|
3
|
+
# the position of the fields block must be preserved
|
4
|
+
belongs_to :author
|
5
|
+
|
6
|
+
fields do
|
7
|
+
title :string
|
8
|
+
price :decimal, :precision=>8, :scale=>2
|
9
|
+
code :string, :limit=>4
|
10
|
+
comments :text
|
11
|
+
timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Author < ActiveRecord::Base
|
2
|
+
|
3
|
+
fields {
|
4
|
+
# The comments must be prerserved
|
5
|
+
number :integer # as well as the order of field declarations,
|
6
|
+
name :string # indendation
|
7
|
+
xxxx :string
|
8
|
+
birthdate :integer, :unique # specifications...
|
9
|
+
# etc.
|
10
|
+
decnum :decimal, :default=>BigDecimal('1.2'), :precision=>11, :scale=>3
|
11
|
+
eventx :datetime
|
12
|
+
}
|
13
|
+
|
14
|
+
has_many :books
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Book < ActiveRecord::Base
|
2
|
+
|
3
|
+
# the position of the fields block must be preserved
|
4
|
+
belongs_to :author
|
5
|
+
|
6
|
+
fields do
|
7
|
+
title :string
|
8
|
+
price :decimal, :precision=>8, :scale=>4
|
9
|
+
code :string, :limit=>4
|
10
|
+
comments :text
|
11
|
+
zzzzz :integer
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/test/schema.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 0) do
|
2
|
+
create_table :authors, :force => true do |t|
|
3
|
+
t.string :name
|
4
|
+
t.integer :number
|
5
|
+
t.date :birthdate
|
6
|
+
t.datetime :event
|
7
|
+
t.decimal :decnum, :precision=>10, :scale=>3, :default=>BigDecimal('1.2')
|
8
|
+
end
|
9
|
+
create_table :books, :force => true do |t|
|
10
|
+
t.integer :author_id
|
11
|
+
t.string :title
|
12
|
+
t.decimal :price, :precision=>8, :scale=>2
|
13
|
+
t.string :code, :limit=>4
|
14
|
+
t.text :comments
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
end
|
data/test/test_diff.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/helper'
|
2
|
+
|
3
|
+
class TestDiff< Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Given model definitions without field declarations" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
load_schema
|
9
|
+
class Author < ActiveRecord::Base
|
10
|
+
has_many :books
|
11
|
+
end
|
12
|
+
class Book < ActiveRecord::Base
|
13
|
+
belongs_to :author
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
should "Find all fields that are not primary or foreing keys" do
|
18
|
+
new_fields, modified_fields, deleted_fields = ModalFields.send(:diff, Author)
|
19
|
+
assert modified_fields.empty?
|
20
|
+
assert deleted_fields.empty?
|
21
|
+
assert_same_elements ["number", "name", "birthdate", "decnum", "event"], new_fields.map{|f| f.name.to_s}
|
22
|
+
new_fields, modified_fields, deleted_fields = ModalFields.send(:diff, Book)
|
23
|
+
assert modified_fields.empty?
|
24
|
+
assert deleted_fields.empty?
|
25
|
+
assert_same_elements ["title", "price", "code", "comments", "created_at", "updated_at"], new_fields.map{|f| f.name.to_s}
|
26
|
+
end
|
27
|
+
|
28
|
+
teardown do
|
29
|
+
TestDiff.send(:remove_const, :Author) if defined?(Author)
|
30
|
+
TestDiff.send(:remove_const, :Book) if defined?(Book)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
context "Given model definitions with up to date field declarations" do
|
36
|
+
|
37
|
+
setup do
|
38
|
+
load_schema
|
39
|
+
class Author < ActiveRecord::Base
|
40
|
+
fields do
|
41
|
+
name :string
|
42
|
+
number :integer
|
43
|
+
birthdate :date
|
44
|
+
event :datetime
|
45
|
+
decnum :decimal, :default=>BigDecimal('1.2'), :precision=>10, :scale=>3
|
46
|
+
end
|
47
|
+
has_many :books
|
48
|
+
end
|
49
|
+
class Book < ActiveRecord::Base
|
50
|
+
fields do
|
51
|
+
title :string
|
52
|
+
price :decimal, :precision=>8, :scale=>2
|
53
|
+
code :string, :limit=>4
|
54
|
+
comments :text
|
55
|
+
timestamps
|
56
|
+
end
|
57
|
+
belongs_to :author
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
should "not find any changes" do
|
62
|
+
new_fields, modified_fields, deleted_fields = ModalFields.send(:diff, Author)
|
63
|
+
# assert new_fields.empty?
|
64
|
+
# assert modified_fields.empty?
|
65
|
+
# assert deleted_fields.empty?
|
66
|
+
assert_same_elements [], new_fields.map{|f| f.name.to_s}
|
67
|
+
assert_same_elements [], modified_fields.map{|f| f.name.to_s}
|
68
|
+
assert_same_elements [], deleted_fields.map{|f| f.name.to_s}
|
69
|
+
|
70
|
+
new_fields, modified_fields, deleted_fields = ModalFields.send(:diff, Book)
|
71
|
+
# assert new_fields.empty?
|
72
|
+
# assert modified_fields.empty?
|
73
|
+
# assert deleted_fields.empty?
|
74
|
+
assert_same_elements [], new_fields.map{|f| f.name.to_s}
|
75
|
+
assert_same_elements [], modified_fields.map{|f| f.name.to_s}
|
76
|
+
assert_same_elements [], deleted_fields.map{|f| f.name.to_s}
|
77
|
+
end
|
78
|
+
|
79
|
+
teardown do
|
80
|
+
TestDiff.send(:remove_const, :Author) if defined?(Author)
|
81
|
+
TestDiff.send(:remove_const, :Book) if defined?(Book)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
context "Given model definitions with unaccurate field declarations" do
|
87
|
+
|
88
|
+
setup do
|
89
|
+
load_schema
|
90
|
+
class Author < ActiveRecord::Base
|
91
|
+
fields do
|
92
|
+
name :string
|
93
|
+
birthdate :datetime
|
94
|
+
nationality :string
|
95
|
+
event :datetime
|
96
|
+
decnum :decimal, :default=>BigDecimal('1.2'), :precision=>10, :scale=>3
|
97
|
+
end
|
98
|
+
has_many :books
|
99
|
+
end
|
100
|
+
class Book < ActiveRecord::Base
|
101
|
+
fields do
|
102
|
+
title :string
|
103
|
+
price :decimal, :precision=>8, :scale=>2
|
104
|
+
code :string, :limit=>5
|
105
|
+
timestamps
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
should "Find schema modifications" do
|
111
|
+
new_fields, modified_fields, deleted_fields = ModalFields.send(:diff, Author)
|
112
|
+
assert_same_elements ['nationality'], deleted_fields.map{|f| f.name.to_s}
|
113
|
+
assert_same_elements ['number'], new_fields.map{|f| f.name.to_s}
|
114
|
+
assert_same_elements ['birthdate'], modified_fields.map{|f| f.name.to_s}
|
115
|
+
new_fields, modified_fields, deleted_fields = ModalFields.send(:diff, Book)
|
116
|
+
assert_same_elements [], deleted_fields.map{|f| f.name.to_s}
|
117
|
+
assert_same_elements ['comments', 'author_id'], new_fields.map{|f| f.name.to_s}
|
118
|
+
assert_same_elements ['code'], modified_fields.map{|f| f.name.to_s}
|
119
|
+
end
|
120
|
+
|
121
|
+
teardown do
|
122
|
+
TestDiff.send(:remove_const, :Author) if defined?(Author)
|
123
|
+
TestDiff.send(:remove_const, :Book) if defined?(Book)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|