cequel 1.0.0.rc1 → 1.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cequel.rb +18 -0
- data/lib/cequel/errors.rb +8 -4
- data/lib/cequel/metal.rb +14 -0
- data/lib/cequel/metal/batch.rb +21 -11
- data/lib/cequel/metal/batch_manager.rb +74 -0
- data/lib/cequel/metal/cql_row_specification.rb +19 -6
- data/lib/cequel/metal/data_set.rb +400 -163
- data/lib/cequel/metal/deleter.rb +45 -11
- data/lib/cequel/metal/incrementer.rb +23 -10
- data/lib/cequel/metal/inserter.rb +19 -6
- data/lib/cequel/metal/keyspace.rb +82 -159
- data/lib/cequel/metal/logger.rb +71 -0
- data/lib/cequel/metal/logging.rb +47 -0
- data/lib/cequel/metal/new_relic_instrumentation.rb +26 -0
- data/lib/cequel/metal/row.rb +36 -10
- data/lib/cequel/metal/row_specification.rb +21 -8
- data/lib/cequel/metal/statement.rb +30 -6
- data/lib/cequel/metal/updater.rb +89 -12
- data/lib/cequel/metal/writer.rb +23 -14
- data/lib/cequel/record.rb +52 -6
- data/lib/cequel/record/association_collection.rb +13 -6
- data/lib/cequel/record/associations.rb +146 -54
- data/lib/cequel/record/belongs_to_association.rb +34 -7
- data/lib/cequel/record/bound.rb +69 -12
- data/lib/cequel/record/bulk_writes.rb +29 -1
- data/lib/cequel/record/callbacks.rb +22 -6
- data/lib/cequel/record/collection.rb +273 -36
- data/lib/cequel/record/configuration_generator.rb +5 -0
- data/lib/cequel/record/data_set_builder.rb +86 -0
- data/lib/cequel/record/dirty.rb +11 -8
- data/lib/cequel/record/errors.rb +38 -4
- data/lib/cequel/record/has_many_association.rb +42 -9
- data/lib/cequel/record/lazy_record_collection.rb +39 -10
- data/lib/cequel/record/mass_assignment.rb +14 -6
- data/lib/cequel/record/persistence.rb +157 -20
- data/lib/cequel/record/properties.rb +147 -24
- data/lib/cequel/record/railtie.rb +15 -2
- data/lib/cequel/record/record_set.rb +504 -75
- data/lib/cequel/record/schema.rb +77 -13
- data/lib/cequel/record/scoped.rb +16 -11
- data/lib/cequel/record/secondary_indexes.rb +42 -6
- data/lib/cequel/record/tasks.rb +2 -1
- data/lib/cequel/record/validations.rb +51 -11
- data/lib/cequel/schema.rb +9 -0
- data/lib/cequel/schema/column.rb +172 -33
- data/lib/cequel/schema/create_table_dsl.rb +62 -31
- data/lib/cequel/schema/keyspace.rb +106 -7
- data/lib/cequel/schema/migration_validator.rb +128 -0
- data/lib/cequel/schema/table.rb +183 -20
- data/lib/cequel/schema/table_property.rb +92 -34
- data/lib/cequel/schema/table_reader.rb +45 -15
- data/lib/cequel/schema/table_synchronizer.rb +101 -43
- data/lib/cequel/schema/table_updater.rb +114 -19
- data/lib/cequel/schema/table_writer.rb +31 -13
- data/lib/cequel/schema/update_table_dsl.rb +71 -40
- data/lib/cequel/type.rb +214 -53
- data/lib/cequel/util.rb +6 -9
- data/lib/cequel/version.rb +2 -1
- data/spec/examples/record/associations_spec.rb +12 -12
- data/spec/examples/record/persistence_spec.rb +5 -5
- data/spec/examples/record/record_set_spec.rb +62 -50
- data/spec/examples/schema/table_synchronizer_spec.rb +37 -11
- data/spec/examples/schema/table_updater_spec.rb +3 -3
- data/spec/examples/spec_helper.rb +2 -11
- data/spec/examples/type_spec.rb +3 -3
- metadata +23 -4
- data/lib/cequel/new_relic_instrumentation.rb +0 -22
@@ -1,36 +1,75 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Schema
|
4
|
-
|
3
|
+
#
|
4
|
+
# Encapsulates a CQL3 storage property defined on a table
|
5
|
+
#
|
5
6
|
class TableProperty
|
7
|
+
# @return [Symbol] name of the property
|
8
|
+
attr_reader :name
|
9
|
+
# @return value of the property
|
10
|
+
attr_reader :value
|
6
11
|
|
7
|
-
|
12
|
+
#
|
13
|
+
# Initialize an instance of the appropriate TableProperty implementation.
|
14
|
+
#
|
15
|
+
# @param (see #initialize)
|
16
|
+
# @api private
|
17
|
+
#
|
18
|
+
def self.build(name, value)
|
19
|
+
clazz =
|
20
|
+
case name.to_sym
|
21
|
+
when :compaction then CompactionProperty
|
22
|
+
when :compression then CompressionProperty
|
23
|
+
else TableProperty
|
24
|
+
end
|
25
|
+
clazz.new(name, value)
|
26
|
+
end
|
8
27
|
|
28
|
+
#
|
29
|
+
# @param name [Symbol] name of the property
|
30
|
+
# @param value value of the property
|
31
|
+
#
|
9
32
|
def initialize(name, value)
|
10
33
|
@name = name
|
11
|
-
|
34
|
+
self.normalized_value = value
|
12
35
|
end
|
36
|
+
class << self; protected :new; end
|
13
37
|
|
38
|
+
#
|
39
|
+
# @return [String] CQL fragment defining this property in a `CREATE
|
40
|
+
# TABLE` statement
|
41
|
+
#
|
14
42
|
def to_cql
|
15
|
-
if Hash === @value
|
16
|
-
map_pairs = @value.each_pair.
|
17
|
-
map { |key, value| "#{quote(key.to_s)} : #{quote(value)}" }.
|
18
|
-
join(', ')
|
19
|
-
value_cql = "{ #{map_pairs} }"
|
20
|
-
else
|
21
|
-
value_cql = quote(@value)
|
22
|
-
end
|
23
43
|
"#{@name} = #{value_cql}"
|
24
44
|
end
|
25
45
|
|
46
|
+
protected
|
47
|
+
|
48
|
+
def normalized_value=(value)
|
49
|
+
@value = value
|
50
|
+
end
|
51
|
+
|
26
52
|
private
|
27
53
|
|
54
|
+
def value_cql
|
55
|
+
quote(@value)
|
56
|
+
end
|
57
|
+
|
28
58
|
def quote(value)
|
29
59
|
CassandraCQL::Statement.quote(value)
|
30
60
|
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# A table property whose value is itself a map of keys and values
|
65
|
+
#
|
66
|
+
# @abstract Inheriting classes must implement
|
67
|
+
# `#normalize_map_property(key, value)`
|
68
|
+
#
|
69
|
+
class MapProperty < TableProperty
|
70
|
+
protected
|
31
71
|
|
32
|
-
def
|
33
|
-
return @value = map unless Hash === map
|
72
|
+
def normalized_value=(map)
|
34
73
|
@value = {}
|
35
74
|
map.each_pair do |key, value|
|
36
75
|
key = key.to_sym
|
@@ -38,30 +77,49 @@ module Cequel
|
|
38
77
|
end
|
39
78
|
end
|
40
79
|
|
80
|
+
private
|
81
|
+
|
82
|
+
def value_cql
|
83
|
+
map_pairs = @value.each_pair
|
84
|
+
.map { |key, value| "#{quote(key.to_s)} : #{quote(value)}" }
|
85
|
+
.join(', ')
|
86
|
+
"{ #{map_pairs} }"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# A property comprising key-value pairs of compaction settings
|
92
|
+
#
|
93
|
+
class CompactionProperty < MapProperty
|
94
|
+
private
|
95
|
+
|
41
96
|
def normalize_map_property(key, value)
|
42
|
-
case
|
43
|
-
when :
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
:sstable_size_in_mb, :tombstone_compaction_interval then value.to_i
|
50
|
-
else value.to_s
|
51
|
-
end
|
52
|
-
when :compression
|
53
|
-
case key
|
54
|
-
when :sstable_compression
|
55
|
-
value.sub(/^org\.apache\.cassandra\.io\.compress\./, '')
|
56
|
-
when :chunk_length_kb then value.to_i
|
57
|
-
when :crc_check_chance then value.to_f
|
58
|
-
else value.to_s
|
59
|
-
end
|
97
|
+
case key
|
98
|
+
when :class
|
99
|
+
value.sub(/^org\.apache\.cassandra\.db\.compaction\./, '')
|
100
|
+
when :bucket_high, :bucket_low, :tombstone_threshold then value.to_f
|
101
|
+
when :max_threshold, :min_threshold, :min_sstable_size,
|
102
|
+
:sstable_size_in_mb, :tombstone_compaction_interval then value.to_i
|
103
|
+
else value.to_s
|
60
104
|
end
|
61
105
|
end
|
62
|
-
|
63
106
|
end
|
64
107
|
|
65
|
-
|
108
|
+
#
|
109
|
+
# A property comprising key-value pairs of compression settings
|
110
|
+
#
|
111
|
+
class CompressionProperty < MapProperty
|
112
|
+
private
|
66
113
|
|
114
|
+
def normalize_map_property(key, value)
|
115
|
+
case key
|
116
|
+
when :sstable_compression
|
117
|
+
value.sub(/^org\.apache\.cassandra\.io\.compress\./, '')
|
118
|
+
when :chunk_length_kb then value.to_i
|
119
|
+
when :crc_check_chance then value.to_f
|
120
|
+
else value.to_s
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
67
125
|
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Schema
|
4
|
-
|
3
|
+
#
|
4
|
+
# A TableReader will query Cassandra's internal representation of a table's
|
5
|
+
# schema, and build a {Table} instance exposing an object representation of
|
6
|
+
# that schema
|
7
|
+
#
|
5
8
|
class TableReader
|
6
|
-
|
7
9
|
COMPOSITE_TYPE_PATTERN =
|
8
10
|
/^org\.apache\.cassandra\.db\.marshal\.CompositeType\((.+)\)$/
|
9
11
|
REVERSED_TYPE_PATTERN =
|
@@ -11,22 +13,40 @@ module Cequel
|
|
11
13
|
COLLECTION_TYPE_PATTERN =
|
12
14
|
/^org\.apache\.cassandra\.db\.marshal\.(List|Set|Map)Type\((.+)\)$/
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
read_repair_chance replicate_on_write]
|
17
|
-
|
16
|
+
# @return [Table] object representation of the table defined in the
|
17
|
+
# database
|
18
18
|
attr_reader :table
|
19
19
|
|
20
|
+
#
|
21
|
+
# Read the schema defined in the database for a given table and return a
|
22
|
+
# {Table} instance
|
23
|
+
#
|
24
|
+
# @param (see #initialize)
|
25
|
+
# @return (see #read)
|
26
|
+
#
|
20
27
|
def self.read(keyspace, table_name)
|
21
28
|
new(keyspace, table_name).read
|
22
29
|
end
|
23
30
|
|
31
|
+
#
|
32
|
+
# @param keyspace [Metal::Keyspace] keyspace to read the table from
|
33
|
+
# @param table_name [Symbol] name of the table to read
|
34
|
+
# @private
|
35
|
+
#
|
24
36
|
def initialize(keyspace, table_name)
|
25
37
|
@keyspace, @table_name = keyspace, table_name
|
26
38
|
@table = Table.new(table_name.to_sym)
|
27
39
|
end
|
28
40
|
private_class_method(:new)
|
29
41
|
|
42
|
+
#
|
43
|
+
# Read table schema from the database
|
44
|
+
#
|
45
|
+
# @return [Table] object representation of table in the database, or
|
46
|
+
# `nil` if no table by given name exists
|
47
|
+
#
|
48
|
+
# @api private
|
49
|
+
#
|
30
50
|
def read
|
31
51
|
if table_data.present?
|
32
52
|
read_partition_keys
|
@@ -38,10 +58,19 @@ module Cequel
|
|
38
58
|
end
|
39
59
|
|
40
60
|
protected
|
61
|
+
|
41
62
|
attr_reader :keyspace, :table_name, :table
|
42
63
|
|
43
64
|
private
|
44
65
|
|
66
|
+
# XXX This gets a lot easier in Cassandra 2.0: all logical columns
|
67
|
+
# (including keys) are returned from the `schema_columns` query, so
|
68
|
+
# there's no need to jump through all these hoops to figure out what the
|
69
|
+
# key columns look like.
|
70
|
+
#
|
71
|
+
# However, this approach works for both 1.2 and 2.0, so better to keep it
|
72
|
+
# for now. It will be worth refactoring this code to take advantage of
|
73
|
+
# 2.0's better interface in a future version of Cequel that targets 2.0+.
|
45
74
|
def read_partition_keys
|
46
75
|
validator = table_data['key_validator']
|
47
76
|
types = parse_composite_types(validator) || [validator]
|
@@ -51,6 +80,7 @@ module Cequel
|
|
51
80
|
end
|
52
81
|
end
|
53
82
|
|
83
|
+
# XXX See comment on {read_partition_keys}
|
54
84
|
def read_clustering_columns
|
55
85
|
column_aliases = JSON.parse(table_data['column_aliases'])
|
56
86
|
comparators = parse_composite_types(table_data['comparator'])
|
@@ -100,16 +130,17 @@ module Cequel
|
|
100
130
|
end
|
101
131
|
|
102
132
|
def read_collection_column(name, collection_type, *internal_types)
|
103
|
-
types = internal_types
|
133
|
+
types = internal_types
|
134
|
+
.map { |internal| Type.lookup_internal(internal) }
|
104
135
|
table.__send__("add_#{collection_type}", name.to_sym, *types)
|
105
136
|
end
|
106
137
|
|
107
138
|
def read_properties
|
108
|
-
table_data.slice(*STORAGE_PROPERTIES).each do |name, value|
|
139
|
+
table_data.slice(*Table::STORAGE_PROPERTIES).each do |name, value|
|
109
140
|
table.add_property(name, value)
|
110
141
|
end
|
111
|
-
compaction = JSON.parse(table_data['compaction_strategy_options'])
|
112
|
-
symbolize_keys
|
142
|
+
compaction = JSON.parse(table_data['compaction_strategy_options'])
|
143
|
+
.symbolize_keys
|
113
144
|
compaction[:class] = table_data['compaction_strategy_class']
|
114
145
|
table.add_property(:compaction, compaction)
|
115
146
|
compression = JSON.parse(table_data['compression_parameters'])
|
@@ -138,12 +169,11 @@ module Cequel
|
|
138
169
|
SELECT * FROM system.schema_columns
|
139
170
|
WHERE keyspace_name = ? AND columnfamily_name = ?
|
140
171
|
CQL
|
141
|
-
column_query.map(&:to_hash)
|
172
|
+
column_query.map(&:to_hash).select do |column|
|
173
|
+
!column.key?('type') || column['type'] == 'regular'
|
174
|
+
end
|
142
175
|
end
|
143
176
|
end
|
144
|
-
|
145
177
|
end
|
146
|
-
|
147
178
|
end
|
148
|
-
|
149
179
|
end
|
@@ -1,9 +1,30 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Schema
|
4
|
-
|
3
|
+
#
|
4
|
+
# Synchronize a table schema in the database with a desired table schema
|
5
|
+
#
|
6
|
+
# @see .apply
|
7
|
+
# @see Keyspace#synchronize_table
|
8
|
+
#
|
5
9
|
class TableSynchronizer
|
6
|
-
|
10
|
+
# @return [Table] table as it is currently defined
|
11
|
+
# @api private
|
12
|
+
attr_reader :existing
|
13
|
+
# @return [Table] table schema as it is desired
|
14
|
+
# @api private
|
15
|
+
attr_reader :updated
|
16
|
+
#
|
17
|
+
# Takes an existing table schema read from the database, and a desired
|
18
|
+
# schema for that table. Modifies the table schema in the database to
|
19
|
+
# match the desired schema, or creates the table as specified if it does
|
20
|
+
# not yet exist
|
21
|
+
#
|
22
|
+
# @param keyspace [Metal::Keyspace] keyspace that contains table
|
23
|
+
# @param existing [Table] table schema as it is currently defined
|
24
|
+
# @param updated [Table] table schema as it is desired
|
25
|
+
# @return [void]
|
26
|
+
# @raise (see #apply)
|
27
|
+
#
|
7
28
|
def self.apply(keyspace, existing, updated)
|
8
29
|
if existing
|
9
30
|
TableUpdater.apply(keyspace, existing.name) do |updater|
|
@@ -14,28 +35,92 @@ module Cequel
|
|
14
35
|
end
|
15
36
|
end
|
16
37
|
|
38
|
+
#
|
39
|
+
# @param updater [TableUpdater] table updater to hold schema
|
40
|
+
# modifications
|
41
|
+
# @param existing [Table] table schema as it is currently defined
|
42
|
+
# @param updated [Table] table schema as it is desired
|
43
|
+
# @return [void]
|
44
|
+
# @private
|
45
|
+
#
|
17
46
|
def initialize(updater, existing, updated)
|
18
47
|
@updater, @existing, @updated = updater, existing, updated
|
19
48
|
end
|
20
49
|
private_class_method :new
|
21
50
|
|
51
|
+
#
|
52
|
+
# Apply the changes needed to synchronize the schema in the database with
|
53
|
+
# the desired schema
|
54
|
+
#
|
55
|
+
# @return [void]
|
56
|
+
# @raise (see MigrationValidator#validate!)
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
#
|
22
60
|
def apply
|
61
|
+
validate!
|
23
62
|
update_keys
|
24
63
|
update_columns
|
25
64
|
update_properties
|
26
65
|
end
|
27
66
|
|
67
|
+
#
|
68
|
+
# Iterate over pairs of (old_key, new_key)
|
69
|
+
#
|
70
|
+
# @yieldparam old_key [Column] key in existing schema
|
71
|
+
# @yieldparam new_key [Column] corresponding key in updated schema
|
72
|
+
# @return [void]
|
73
|
+
#
|
74
|
+
# @api private
|
75
|
+
#
|
76
|
+
def each_key_pair(&block)
|
77
|
+
existing.key_columns.zip(updated.key_columns, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Iterate over pairs of (old_column, new_column)
|
82
|
+
#
|
83
|
+
# @yieldparam old_column [Column] column in existing schema
|
84
|
+
# @yieldparam new_column [Column] corresponding column in updated schema
|
85
|
+
# @return [void]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
#
|
89
|
+
def each_data_column_pair(&block)
|
90
|
+
if existing.compact_storage? && existing.clustering_columns.any?
|
91
|
+
yield existing.data_columns.first, updated.data_columns.first
|
92
|
+
else
|
93
|
+
old_columns = existing.data_columns.index_by { |col| col.name }
|
94
|
+
new_columns = updated.data_columns.index_by { |col| col.name }
|
95
|
+
all_column_names = (old_columns.keys + new_columns.keys).tap(&:uniq!)
|
96
|
+
all_column_names.each do |name|
|
97
|
+
yield old_columns[name], new_columns[name]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
#
|
103
|
+
# Iterate over pairs of (old_clustering_column, new_clustering_column)
|
104
|
+
#
|
105
|
+
# @yieldparam old_clustering_column [Column] key in existing schema
|
106
|
+
# @yieldparam new_clustering_column [Column] corresponding key in updated
|
107
|
+
# schema
|
108
|
+
# @return [void]
|
109
|
+
#
|
110
|
+
# @api private
|
111
|
+
#
|
112
|
+
def each_clustering_column_pair(&block)
|
113
|
+
existing.clustering_columns.zip(updated.clustering_columns, &block)
|
114
|
+
end
|
115
|
+
|
28
116
|
protected
|
29
|
-
|
117
|
+
|
118
|
+
attr_reader :updater
|
30
119
|
|
31
120
|
private
|
32
121
|
|
33
122
|
def update_keys
|
34
123
|
each_key_pair do |old_key, new_key|
|
35
|
-
if old_key.type != new_key.type
|
36
|
-
raise InvalidSchemaMigration,
|
37
|
-
"Can't change type of key column #{old_key.name} from #{old_key.type} to #{new_key.type}"
|
38
|
-
end
|
39
124
|
if old_key.name != new_key.name
|
40
125
|
updater.rename_column(old_key.name || :column1, new_key.name)
|
41
126
|
end
|
@@ -43,24 +128,19 @@ module Cequel
|
|
43
128
|
end
|
44
129
|
|
45
130
|
def update_columns
|
46
|
-
|
131
|
+
each_data_column_pair do |old_column, new_column|
|
47
132
|
if old_column.nil?
|
48
133
|
add_column(new_column)
|
49
134
|
elsif new_column
|
50
|
-
if old_column.class != new_column.class
|
51
|
-
raise InvalidSchemaMigration,
|
52
|
-
"Can't change #{old_column.name} from #{old_column.class.name.demodulize} to #{new_column.class.name.demodulize}"
|
53
|
-
end
|
54
135
|
update_column(old_column, new_column)
|
136
|
+
update_index(old_column, new_column)
|
55
137
|
end
|
56
138
|
end
|
57
139
|
end
|
58
140
|
|
59
141
|
def add_column(column)
|
60
142
|
updater.add_data_column(column)
|
61
|
-
if column.indexed?
|
62
|
-
updater.create_index(column.name, column.index_name)
|
63
|
-
end
|
143
|
+
updater.create_index(column.name, column.index_name) if column.indexed?
|
64
144
|
end
|
65
145
|
|
66
146
|
def update_column(old_column, new_column)
|
@@ -70,6 +150,9 @@ module Cequel
|
|
70
150
|
if old_column.type != new_column.type
|
71
151
|
updater.change_column(new_column.name, new_column.type)
|
72
152
|
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def update_index(old_column, new_column)
|
73
156
|
if !old_column.indexed? && new_column.indexed?
|
74
157
|
updater.create_index(new_column.name, new_column.index_name)
|
75
158
|
elsif old_column.indexed? && !new_column.indexed?
|
@@ -88,34 +171,9 @@ module Cequel
|
|
88
171
|
updater.change_properties(changes) if changes.any?
|
89
172
|
end
|
90
173
|
|
91
|
-
def
|
92
|
-
|
93
|
-
raise InvalidSchemaMigration,
|
94
|
-
"Existing partition keys #{existing.partition_key_columns.map { |key| key.name }.join(',')} differ from specified partition keys #{updated.partition_key_columns.map { |key| key.name }.join(',')}"
|
95
|
-
end
|
96
|
-
if existing.clustering_columns.length != updated.clustering_columns.length
|
97
|
-
raise InvalidSchemaMigration,
|
98
|
-
"Existing clustering keys #{existing.clustering_columns.map { |key| key.name }.join(',')} differ from specified clustering keys #{updated.clustering_columns.map { |key| key.name }.join(',')}"
|
99
|
-
end
|
100
|
-
existing.partition_key_columns.zip(updated.partition_key_columns, &block)
|
101
|
-
existing.clustering_columns.zip(updated.clustering_columns, &block)
|
102
|
-
end
|
103
|
-
|
104
|
-
def each_column_pair(&block)
|
105
|
-
if existing.compact_storage? && existing.clustering_columns.any?
|
106
|
-
yield existing.data_columns.first, updated.data_columns.first
|
107
|
-
else
|
108
|
-
old_columns = existing.data_columns.index_by { |col| col.name }
|
109
|
-
new_columns = updated.data_columns.index_by { |col| col.name }
|
110
|
-
all_column_names = (old_columns.keys + new_columns.keys).tap(&:uniq!)
|
111
|
-
all_column_names.each do |name|
|
112
|
-
yield old_columns[name], new_columns[name]
|
113
|
-
end
|
114
|
-
end
|
174
|
+
def validate!
|
175
|
+
MigrationValidator.validate!(self)
|
115
176
|
end
|
116
|
-
|
117
177
|
end
|
118
|
-
|
119
178
|
end
|
120
|
-
|
121
179
|
end
|