migrant 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/datatype/base.rb +9 -9
- data/lib/migrant/model_extensions.rb +10 -4
- data/lib/migrant/schema.rb +12 -7
- data/migrant.gemspec +5 -2
- data/test/test_migration_generator.rb +25 -18
- data/test/test_validations.rb +34 -0
- data/test/verified_output/migrations/created_at.rb +11 -0
- metadata +8 -5
- data/lib/datatype/hash.rb +0 -10
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/datatype/base.rb
CHANGED
@@ -3,7 +3,7 @@ module DataType
|
|
3
3
|
|
4
4
|
class Base
|
5
5
|
attr_accessor :aliases
|
6
|
-
|
6
|
+
|
7
7
|
# Pass the developer's ActiveRecord::Base structure and we'll
|
8
8
|
# decide the best structure
|
9
9
|
def initialize(options={})
|
@@ -12,25 +12,25 @@ module DataType
|
|
12
12
|
@field = options.delete(:field)
|
13
13
|
@aliases = options.delete(:was) || Array.new
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
# Default is 'ye good ol varchar(255)
|
17
17
|
def column
|
18
18
|
{:type => :string}.merge(@options)
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def mock
|
22
22
|
@value || self.class.default_mock
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def self.default_mock
|
26
26
|
"Some string"
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# Decides if and how a column will be changed
|
30
30
|
# Provide the details of a previously column, or simply nil to create a new column
|
31
|
-
def structure_changes_from(current_structure = nil)
|
31
|
+
def structure_changes_from(current_structure = nil)
|
32
32
|
new_structure = column
|
33
|
-
|
33
|
+
|
34
34
|
if current_structure
|
35
35
|
# General RDBMS data loss scenarios
|
36
36
|
raise DataType::DangerousMigration if (new_structure[:type] != :text && [:string, :text].include?(current_structure[:type]) && new_structure[:type] != current_structure[:type])
|
@@ -46,7 +46,7 @@ module DataType
|
|
46
46
|
column
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def self.migrant_data_type?; true; end
|
51
51
|
end
|
52
52
|
end
|
@@ -58,9 +58,9 @@ require 'datatype/date'
|
|
58
58
|
require 'datatype/fixnum'
|
59
59
|
require 'datatype/float'
|
60
60
|
require 'datatype/foreign_key'
|
61
|
-
require 'datatype/hash'
|
62
61
|
require 'datatype/polymorphic'
|
63
62
|
require 'datatype/range'
|
64
63
|
require 'datatype/string'
|
65
64
|
require 'datatype/symbol'
|
66
65
|
require 'datatype/time'
|
66
|
+
|
@@ -4,29 +4,34 @@ module Migrant
|
|
4
4
|
def structure(&block)
|
5
5
|
# Using instance_*evil* to get the neater DSL on the models.
|
6
6
|
# So, my_field in the structure block actually calls Migrant::Schema.my_field
|
7
|
-
|
7
|
+
|
8
8
|
if self.superclass == ActiveRecord::Base
|
9
9
|
@schema ||= Schema.new
|
10
10
|
@schema.add_associations(self.reflect_on_all_associations)
|
11
11
|
@schema.define_structure(&block)
|
12
|
+
@schema.validations.each do |field, validation|
|
13
|
+
validation = (validation.class == Hash)? validation : { validation => true }
|
14
|
+
self.validates(field, validation)
|
15
|
+
end
|
12
16
|
else
|
13
17
|
self.superclass.structure(&block) # For STI, cascade all fields onto the parent model
|
14
18
|
@schema = InheritedSchema.new(self.superclass.schema)
|
19
|
+
|
15
20
|
end
|
16
21
|
end
|
17
|
-
|
22
|
+
|
18
23
|
# Same as defining a structure block, but with no attributes besides
|
19
24
|
# relationships (such as in a many-to-many)
|
20
25
|
def no_structure
|
21
26
|
structure {}
|
22
27
|
end
|
23
|
-
|
28
|
+
|
24
29
|
def mock(attributes={}, recursive=true)
|
25
30
|
attribs = @schema.columns.reject { |column| column.is_a?(DataType::ForeignKey)}.collect { |name, data_type| [name, data_type.mock] }.flatten
|
26
31
|
|
27
32
|
# Only recurse to one level, otherwise things get way too complicated
|
28
33
|
if recursive
|
29
|
-
attribs += self.reflect_on_all_associations(:belongs_to).collect do |association|
|
34
|
+
attribs += self.reflect_on_all_associations(:belongs_to).collect do |association|
|
30
35
|
begin
|
31
36
|
(association.klass.respond_to?(:mock))? [association.name, association.klass.mock({}, false)] : nil
|
32
37
|
rescue NameError; nil; end # User hasn't defined association, just skip it
|
@@ -36,3 +41,4 @@ module Migrant
|
|
36
41
|
end
|
37
42
|
end
|
38
43
|
end
|
44
|
+
|
data/lib/migrant/schema.rb
CHANGED
@@ -9,23 +9,19 @@ module Migrant
|
|
9
9
|
# into a schema on that model class by calling method_missing(my_field)
|
10
10
|
# and deciding what the best schema type is for the user's requiredments
|
11
11
|
class Schema
|
12
|
-
|
13
|
-
# So, I am doing this rather than inhering from SimpleObject, just live with it.
|
14
|
-
# instance_methods.each { |m| raise 'fuckoff' if m == 'system' ; undef_method unless ['__send__', 'object_id'].include?(m) }
|
15
|
-
|
16
|
-
attr_accessor :indexes, :columns, :methods_to_alias
|
12
|
+
attr_accessor :indexes, :columns, :validations, :methods_to_alias
|
17
13
|
|
18
14
|
def initialize
|
19
15
|
@proxy = SchemaProxy.new(self)
|
20
16
|
@columns = Hash.new
|
21
17
|
@indexes = Array.new
|
18
|
+
@validations = Hash.new
|
22
19
|
@methods_to_alias = Array.new
|
23
20
|
end
|
24
21
|
|
25
22
|
def define_structure(&block)
|
26
23
|
# Runs method_missing on columns given in the model "structure" DSL
|
27
24
|
@proxy.translate_fancy_dsl(&block) if block_given?
|
28
|
-
# self.instance_eval(&block) if block_given?
|
29
25
|
end
|
30
26
|
|
31
27
|
def add_associations(associations)
|
@@ -58,8 +54,17 @@ module Migrant
|
|
58
54
|
def add_field(field, data_type = nil, options = {})
|
59
55
|
data_type = DataType::String if data_type.nil?
|
60
56
|
|
57
|
+
# Fields that do special things go here.
|
58
|
+
if field == :timestamps
|
59
|
+
add_field(:updated_at, :datetime)
|
60
|
+
add_field(:created_at, :datetime)
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
|
61
64
|
# Add index if explicitly asked
|
62
65
|
@indexes << field if options.delete(:index) || data_type.class.to_s == 'Hash' && data_type.delete(:index)
|
66
|
+
@validations[field] = options.delete(:validates) if options[:validates]
|
67
|
+
|
63
68
|
options.merge!(:field => field)
|
64
69
|
|
65
70
|
# Matches: description DataType::Paragraph, :index => true
|
@@ -112,7 +117,7 @@ module Migrant
|
|
112
117
|
|
113
118
|
def method_missing(*args, &block)
|
114
119
|
field = args.slice!(0)
|
115
|
-
data_type = args.slice!(0) unless args.first.nil?
|
120
|
+
data_type = args.slice!(0) unless args.first.nil? || args.first.respond_to?(:keys)
|
116
121
|
|
117
122
|
@binding.add_field(field, data_type, args.extract_options!)
|
118
123
|
end
|
data/migrant.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{migrant}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Pascal Houliston"]
|
@@ -30,7 +30,6 @@ Gem::Specification.new do |s|
|
|
30
30
|
"lib/datatype/fixnum.rb",
|
31
31
|
"lib/datatype/float.rb",
|
32
32
|
"lib/datatype/foreign_key.rb",
|
33
|
-
"lib/datatype/hash.rb",
|
34
33
|
"lib/datatype/polymorphic.rb",
|
35
34
|
"lib/datatype/range.rb",
|
36
35
|
"lib/datatype/string.rb",
|
@@ -83,12 +82,14 @@ Gem::Specification.new do |s|
|
|
83
82
|
"test/rails_app/vendor/plugins/.gitkeep",
|
84
83
|
"test/test_data_schema.rb",
|
85
84
|
"test/test_migration_generator.rb",
|
85
|
+
"test/test_validations.rb",
|
86
86
|
"test/verified_output/migrations/business_id.rb",
|
87
87
|
"test/verified_output/migrations/create_business_categories.rb",
|
88
88
|
"test/verified_output/migrations/create_businesses.rb",
|
89
89
|
"test/verified_output/migrations/create_categories.rb",
|
90
90
|
"test/verified_output/migrations/create_reviews.rb",
|
91
91
|
"test/verified_output/migrations/create_users.rb",
|
92
|
+
"test/verified_output/migrations/created_at.rb",
|
92
93
|
"test/verified_output/migrations/estimated_value_notes.rb",
|
93
94
|
"test/verified_output/migrations/landline.rb"
|
94
95
|
]
|
@@ -123,12 +124,14 @@ Gem::Specification.new do |s|
|
|
123
124
|
"test/rails_app/test/test_helper.rb",
|
124
125
|
"test/test_data_schema.rb",
|
125
126
|
"test/test_migration_generator.rb",
|
127
|
+
"test/test_validations.rb",
|
126
128
|
"test/verified_output/migrations/business_id.rb",
|
127
129
|
"test/verified_output/migrations/create_business_categories.rb",
|
128
130
|
"test/verified_output/migrations/create_businesses.rb",
|
129
131
|
"test/verified_output/migrations/create_categories.rb",
|
130
132
|
"test/verified_output/migrations/create_reviews.rb",
|
131
133
|
"test/verified_output/migrations/create_users.rb",
|
134
|
+
"test/verified_output/migrations/created_at.rb",
|
132
135
|
"test/verified_output/migrations/estimated_value_notes.rb",
|
133
136
|
"test/verified_output/migrations/landline.rb"
|
134
137
|
]
|
@@ -4,7 +4,7 @@ require 'rake'
|
|
4
4
|
def rake_migrate
|
5
5
|
Dir.chdir(Rails.root.join('.')) do
|
6
6
|
Rake::Task['db:migrate'].execute
|
7
|
-
end
|
7
|
+
end
|
8
8
|
end
|
9
9
|
|
10
10
|
|
@@ -15,13 +15,13 @@ class TestMigrationGenerator < Test::Unit::TestCase
|
|
15
15
|
if migration_file.include?(template)
|
16
16
|
correct = File.join(File.dirname(__FILE__), 'verified_output', 'migrations', template+'.rb')
|
17
17
|
assert_equal(File.open(correct, 'r') { |r| r.read}.strip,
|
18
|
-
File.open(migration_file, 'r') { |r| r.read}.strip,
|
18
|
+
File.open(migration_file, 'r') { |r| r.read}.strip,
|
19
19
|
"Generated migration #{migration_file} does not match template #{correct}")
|
20
20
|
rake_migrate
|
21
21
|
return
|
22
22
|
end
|
23
23
|
end
|
24
|
-
flunk "No migration could be found"
|
24
|
+
flunk "No migration could be found"
|
25
25
|
end
|
26
26
|
|
27
27
|
context "The migration generator" do
|
@@ -35,7 +35,7 @@ class TestMigrationGenerator < Test::Unit::TestCase
|
|
35
35
|
end
|
36
36
|
rake_migrate
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
should "generate a migration for new added fields" do
|
40
40
|
Business.structure do
|
41
41
|
estimated_value 5000.0
|
@@ -43,41 +43,48 @@ class TestMigrationGenerator < Test::Unit::TestCase
|
|
43
43
|
end
|
44
44
|
run_against_template('estimated_value_notes')
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
should "generate a migration to alter existing columns where no data loss would occur" do
|
48
48
|
Business.structure do
|
49
49
|
landline :text
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
run_against_template('landline')
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
should "generate a migration to alter existing columns while adding a new table" do
|
56
56
|
load File.join(File.dirname(__FILE__), 'additional_models', 'review.rb')
|
57
57
|
User.belongs_to(:business) # To generate a business_id on a User
|
58
58
|
User.no_structure # To force schema update
|
59
|
-
|
59
|
+
|
60
60
|
run_against_template('create_reviews')
|
61
61
|
run_against_template('business_id')
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
|
+
should "generate created_at and updated_at when given the column timestamps" do
|
65
|
+
Business.structure do
|
66
|
+
timestamps
|
67
|
+
end
|
68
|
+
run_against_template('created_at')
|
69
|
+
end
|
70
|
+
|
64
71
|
should "not change existing columns where data loss may occur" do
|
65
72
|
Business.structure do
|
66
|
-
landline :integer # Was previously a string, which obviously may incur data loss
|
73
|
+
landline :integer # Was previously a string, which obviously may incur data loss
|
67
74
|
end
|
68
|
-
assert_equal(false, Migrant::MigrationGenerator.new.run, "MigrationGenerator ran a dangerous migration!")
|
75
|
+
assert_equal(false, Migrant::MigrationGenerator.new.run, "MigrationGenerator ran a dangerous migration!")
|
69
76
|
Business.structure do
|
70
77
|
landline :text # Undo our bad for the next tests
|
71
|
-
end
|
78
|
+
end
|
72
79
|
end
|
73
|
-
|
80
|
+
|
74
81
|
should "exit immediately if there are pending migrations" do
|
75
82
|
manual_migration = Rails.root.join("db/migrate/9999999999999999_my_new_migration.rb")
|
76
83
|
File.open(manual_migration, 'w') { |f| f.write ' ' }
|
77
84
|
assert_equal(false, Migrant::MigrationGenerator.new.run)
|
78
85
|
File.delete(manual_migration)
|
79
86
|
end
|
80
|
-
|
87
|
+
|
81
88
|
should "still create sequential migrations for the folks not using timestamps" do
|
82
89
|
Business.structure do
|
83
90
|
new_field_i_made_up
|
@@ -85,8 +92,8 @@ class TestMigrationGenerator < Test::Unit::TestCase
|
|
85
92
|
# Remove migrations
|
86
93
|
ActiveRecord::Base.timestamped_migrations = false
|
87
94
|
assert_equal true, Migrant::MigrationGenerator.new.run, "Migration Generator reported an error"
|
88
|
-
ActiveRecord::Base.timestamped_migrations = true
|
89
|
-
|
95
|
+
ActiveRecord::Base.timestamped_migrations = true
|
96
|
+
|
90
97
|
assert_equal(Dir.glob(File.join(File.dirname(__FILE__), 'rails_app', 'db' ,'migrate', '*.rb')).select { |migration_file| migration_file.include?('new_field_i_made_up') }.length,
|
91
98
|
1,
|
92
99
|
"Migration should have been generated (without a duplicate)")
|
@@ -98,7 +105,7 @@ class TestMigrationGenerator < Test::Unit::TestCase
|
|
98
105
|
test_date_mockup DataType::Date
|
99
106
|
test_float_mockup DataType::Float
|
100
107
|
test_range_mockup DataType::Range
|
101
|
-
|
108
|
+
|
102
109
|
end
|
103
110
|
BusinessCategory.belongs_to(:nonexistant_class, :polymorphic => true)
|
104
111
|
assert_equal true, Migrant::MigrationGenerator.new.run, "Migration Generator reported an error"
|
@@ -111,4 +118,4 @@ class TestMigrationGenerator < Test::Unit::TestCase
|
|
111
118
|
|
112
119
|
end
|
113
120
|
end
|
114
|
-
|
121
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestValidations < Test::Unit::TestCase
|
4
|
+
should "validate via ActiveRecord when the validates symbol is supplied" do
|
5
|
+
Business.structure do
|
6
|
+
website :string, :validates => :presence
|
7
|
+
end
|
8
|
+
|
9
|
+
business = Business.create
|
10
|
+
assert(business.errors.include?(:website), "Validation was not applied")
|
11
|
+
end
|
12
|
+
|
13
|
+
should "validate via ActiveRecord when the full validation hash is supplied" do
|
14
|
+
Category.structure do
|
15
|
+
summary :string, :validates => { :format => { :with => /Symphony\d/ } }
|
16
|
+
end
|
17
|
+
|
18
|
+
bad_category = Category.create
|
19
|
+
good_category = Category.create(:summary => "Symphony5")
|
20
|
+
assert(bad_category.errors.include?(:summary), "Validation was not applied")
|
21
|
+
assert(!good_category.errors.include?(:summary), "Validation options were incorrect")
|
22
|
+
end
|
23
|
+
|
24
|
+
should "validate via ActiveRecord when no field name is given" do
|
25
|
+
User.structure do
|
26
|
+
email :validates => :presence
|
27
|
+
end
|
28
|
+
|
29
|
+
user = User.create
|
30
|
+
assert(user.errors.include?(:email), "Validation was not applied")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class BusinessesModifyFieldsUpdatedAtCreatedAt < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :businesses, :updated_at, :datetime
|
4
|
+
add_column :businesses, :created_at, :datetime
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
remove_column :businesses, :updated_at
|
9
|
+
remove_column :businesses, :created_at
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Pascal Houliston
|
@@ -163,7 +163,6 @@ files:
|
|
163
163
|
- lib/datatype/fixnum.rb
|
164
164
|
- lib/datatype/float.rb
|
165
165
|
- lib/datatype/foreign_key.rb
|
166
|
-
- lib/datatype/hash.rb
|
167
166
|
- lib/datatype/polymorphic.rb
|
168
167
|
- lib/datatype/range.rb
|
169
168
|
- lib/datatype/string.rb
|
@@ -216,12 +215,14 @@ files:
|
|
216
215
|
- test/rails_app/vendor/plugins/.gitkeep
|
217
216
|
- test/test_data_schema.rb
|
218
217
|
- test/test_migration_generator.rb
|
218
|
+
- test/test_validations.rb
|
219
219
|
- test/verified_output/migrations/business_id.rb
|
220
220
|
- test/verified_output/migrations/create_business_categories.rb
|
221
221
|
- test/verified_output/migrations/create_businesses.rb
|
222
222
|
- test/verified_output/migrations/create_categories.rb
|
223
223
|
- test/verified_output/migrations/create_reviews.rb
|
224
224
|
- test/verified_output/migrations/create_users.rb
|
225
|
+
- test/verified_output/migrations/created_at.rb
|
225
226
|
- test/verified_output/migrations/estimated_value_notes.rb
|
226
227
|
- test/verified_output/migrations/landline.rb
|
227
228
|
has_rdoc: true
|
@@ -238,7 +239,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
238
239
|
requirements:
|
239
240
|
- - ">="
|
240
241
|
- !ruby/object:Gem::Version
|
241
|
-
hash: -
|
242
|
+
hash: -984502067
|
242
243
|
segments:
|
243
244
|
- 0
|
244
245
|
version: "0"
|
@@ -284,11 +285,13 @@ test_files:
|
|
284
285
|
- test/rails_app/test/test_helper.rb
|
285
286
|
- test/test_data_schema.rb
|
286
287
|
- test/test_migration_generator.rb
|
288
|
+
- test/test_validations.rb
|
287
289
|
- test/verified_output/migrations/business_id.rb
|
288
290
|
- test/verified_output/migrations/create_business_categories.rb
|
289
291
|
- test/verified_output/migrations/create_businesses.rb
|
290
292
|
- test/verified_output/migrations/create_categories.rb
|
291
293
|
- test/verified_output/migrations/create_reviews.rb
|
292
294
|
- test/verified_output/migrations/create_users.rb
|
295
|
+
- test/verified_output/migrations/created_at.rb
|
293
296
|
- test/verified_output/migrations/estimated_value_notes.rb
|
294
297
|
- test/verified_output/migrations/landline.rb
|