migrant 0.1.5 → 0.2.0
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/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
|