activerecord 1.15.6 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +2454 -34
- data/README +1 -1
- data/RUNNING_UNIT_TESTS +3 -34
- data/Rakefile +98 -77
- data/install.rb +1 -1
- data/lib/active_record.rb +13 -22
- data/lib/active_record/aggregations.rb +38 -49
- data/lib/active_record/associations.rb +452 -333
- data/lib/active_record/associations/association_collection.rb +66 -20
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +38 -18
- data/lib/active_record/associations/has_one_association.rb +30 -14
- data/lib/active_record/attribute_methods.rb +253 -0
- data/lib/active_record/base.rb +719 -494
- data/lib/active_record/calculations.rb +62 -63
- data/lib/active_record/callbacks.rb +57 -83
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
- data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
- data/lib/active_record/fixtures.rb +503 -113
- data/lib/active_record/locking/optimistic.rb +72 -34
- data/lib/active_record/migration.rb +80 -57
- data/lib/active_record/observer.rb +13 -10
- data/lib/active_record/query_cache.rb +16 -57
- data/lib/active_record/reflection.rb +35 -38
- data/lib/active_record/schema.rb +5 -5
- data/lib/active_record/schema_dumper.rb +35 -13
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
- data/lib/active_record/timestamp.rb +20 -21
- data/lib/active_record/transactions.rb +39 -43
- data/lib/active_record/validations.rb +256 -107
- data/lib/active_record/version.rb +3 -3
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +15 -2
- data/test/abstract_unit.rb +24 -17
- data/test/active_schema_test_mysql.rb +20 -8
- data/test/adapter_test.rb +23 -5
- data/test/adapter_test_sqlserver.rb +15 -1
- data/test/aggregations_test.rb +16 -1
- data/test/all.sh +2 -2
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +51 -30
- data/test/associations/cascaded_eager_loading_test.rb +1 -29
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +42 -6
- data/test/associations/extension_test.rb +6 -1
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +47 -16
- data/test/associations_test.rb +449 -226
- data/test/attribute_methods_test.rb +97 -0
- data/test/base_test.rb +251 -105
- data/test/binary_test.rb +22 -27
- data/test/calculations_test.rb +37 -5
- data/test/callbacks_test.rb +23 -0
- data/test/connection_test_firebird.rb +2 -2
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_mysql/connection.rb +3 -0
- data/test/connections/native_sqlite/connection.rb +5 -14
- data/test/connections/native_sqlite3/connection.rb +5 -14
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
- data/test/datatype_test_postgresql.rb +178 -27
- data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
- data/test/defaults_test.rb +8 -1
- data/test/deprecated_finder_test.rb +7 -128
- data/test/finder_test.rb +192 -54
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author.rb +12 -5
- data/test/fixtures/binaries.yml +130 -435
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +8 -1
- data/test/fixtures/computer.rb +1 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
- data/test/fixtures/db_definitions/firebird.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase.sql +5 -0
- data/test/fixtures/db_definitions/openbase.sql +41 -25
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +87 -58
- data/test/fixtures/db_definitions/postgresql2.sql +1 -2
- data/test/fixtures/db_definitions/schema.rb +280 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +4 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
- data/test/fixtures/db_definitions/sybase.sql +4 -0
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +0 -3
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixins.yml +2 -100
- data/test/fixtures/parrot.rb +13 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +1 -0
- data/test/fixtures/project.rb +3 -2
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ship.rb +3 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/tagging.rb +4 -0
- data/test/fixtures/taggings.yml +8 -1
- data/test/fixtures/topic.rb +13 -1
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures_test.rb +205 -24
- data/test/inheritance_test.rb +7 -1
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +1 -1
- data/test/locking_test.rb +85 -2
- data/test/migration_test.rb +206 -40
- data/test/mixin_test.rb +13 -515
- data/test/pk_test.rb +3 -6
- data/test/query_cache_test.rb +104 -0
- data/test/reflection_test.rb +16 -0
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_dumper_test.rb +38 -3
- data/test/serialization_test.rb +47 -0
- data/test/transactions_test.rb +74 -23
- data/test/unconnected_test.rb +1 -1
- data/test/validations_test.rb +322 -32
- data/test/xml_serialization_test.rb +121 -44
- metadata +48 -41
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -85
- data/lib/active_record/acts/list.rb +0 -256
- data/lib/active_record/acts/nested_set.rb +0 -211
- data/lib/active_record/acts/tree.rb +0 -96
- data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
- data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
- data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
- data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
- data/lib/active_record/deprecated_associations.rb +0 -104
- data/lib/active_record/deprecated_finders.rb +0 -44
- data/lib/active_record/vendor/simple.rb +0 -693
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -58
- data/test/connections/native_sqlserver/connection.rb +0 -23
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
- data/test/deprecated_associations_test.rb +0 -396
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
- data/test/fixtures/db_definitions/mysql.sql +0 -234
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
- data/test/fixtures/db_definitions/sqlserver.sql +0 -243
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
- data/test/fixtures/mixin.rb +0 -63
- data/test/mixin_nested_set_test.rb +0 -196
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'active_record/connection_adapters/sqlite_adapter'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Base
|
5
|
+
# sqlite3 adapter reuses sqlite_connection.
|
6
|
+
def self.sqlite3_connection(config) # :nodoc:
|
7
|
+
parse_sqlite_config!(config)
|
8
|
+
|
9
|
+
unless self.class.const_defined?(:SQLite3)
|
10
|
+
require_library_or_gem(config[:adapter])
|
11
|
+
end
|
12
|
+
|
13
|
+
db = SQLite3::Database.new(
|
14
|
+
config[:database],
|
15
|
+
:results_as_hash => true,
|
16
|
+
:type_translation => false
|
17
|
+
)
|
18
|
+
|
19
|
+
db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
|
20
|
+
|
21
|
+
ConnectionAdapters::SQLite3Adapter.new(db, logger)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ConnectionAdapters #:nodoc:
|
26
|
+
class SQLite3Adapter < SQLiteAdapter # :nodoc:
|
27
|
+
def table_structure(table_name)
|
28
|
+
returning structure = @connection.table_info(table_name) do
|
29
|
+
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,33 +1,11 @@
|
|
1
|
-
# Author: Luke Holden <lholden@cablelan.net>
|
2
|
-
# Updated for SQLite3: Jamis Buck <jamis@37signals.com>
|
3
|
-
|
4
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
5
2
|
|
6
3
|
module ActiveRecord
|
7
4
|
class Base
|
8
5
|
class << self
|
9
|
-
# sqlite3 adapter reuses sqlite_connection.
|
10
|
-
def sqlite3_connection(config) # :nodoc:
|
11
|
-
parse_config!(config)
|
12
|
-
|
13
|
-
unless self.class.const_defined?(:SQLite3)
|
14
|
-
require_library_or_gem(config[:adapter])
|
15
|
-
end
|
16
|
-
|
17
|
-
db = SQLite3::Database.new(
|
18
|
-
config[:database],
|
19
|
-
:results_as_hash => true,
|
20
|
-
:type_translation => false
|
21
|
-
)
|
22
|
-
|
23
|
-
db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
|
24
|
-
|
25
|
-
ConnectionAdapters::SQLite3Adapter.new(db, logger)
|
26
|
-
end
|
27
|
-
|
28
6
|
# Establishes a connection to the database that's used by all Active Record objects
|
29
7
|
def sqlite_connection(config) # :nodoc:
|
30
|
-
|
8
|
+
parse_sqlite_config!(config)
|
31
9
|
|
32
10
|
unless self.class.const_defined?(:SQLite)
|
33
11
|
require_library_or_gem(config[:adapter])
|
@@ -47,7 +25,7 @@ module ActiveRecord
|
|
47
25
|
end
|
48
26
|
|
49
27
|
private
|
50
|
-
def
|
28
|
+
def parse_sqlite_config!(config)
|
51
29
|
config[:database] ||= config[:dbfile]
|
52
30
|
# Require database.
|
53
31
|
unless config[:database]
|
@@ -56,7 +34,7 @@ module ActiveRecord
|
|
56
34
|
|
57
35
|
# Allow database path relative to RAILS_ROOT, but only if
|
58
36
|
# the database path is not the special path that tells
|
59
|
-
# Sqlite build a database only in memory.
|
37
|
+
# Sqlite to build a database only in memory.
|
60
38
|
if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
|
61
39
|
config[:database] = File.expand_path(config[:database], RAILS_ROOT)
|
62
40
|
end
|
@@ -73,16 +51,16 @@ module ActiveRecord
|
|
73
51
|
when "\0" then "%00"
|
74
52
|
when "%" then "%25"
|
75
53
|
end
|
76
|
-
end
|
54
|
+
end
|
77
55
|
end
|
78
|
-
|
56
|
+
|
79
57
|
def binary_to_string(value)
|
80
58
|
value.gsub(/%00|%25/n) do |b|
|
81
59
|
case b
|
82
60
|
when "%00" then "\0"
|
83
61
|
when "%25" then "%"
|
84
62
|
end
|
85
|
-
end
|
63
|
+
end
|
86
64
|
end
|
87
65
|
end
|
88
66
|
end
|
@@ -105,14 +83,23 @@ module ActiveRecord
|
|
105
83
|
def requires_reloading?
|
106
84
|
true
|
107
85
|
end
|
108
|
-
|
86
|
+
|
87
|
+
def disconnect!
|
88
|
+
super
|
89
|
+
@connection.close rescue nil
|
90
|
+
end
|
91
|
+
|
109
92
|
def supports_count_distinct? #:nodoc:
|
110
|
-
|
93
|
+
sqlite_version >= '3.2.6'
|
94
|
+
end
|
95
|
+
|
96
|
+
def supports_autoincrement? #:nodoc:
|
97
|
+
sqlite_version >= '3.1.0'
|
111
98
|
end
|
112
99
|
|
113
100
|
def native_database_types #:nodoc:
|
114
101
|
{
|
115
|
-
:primary_key =>
|
102
|
+
:primary_key => default_primary_key_type,
|
116
103
|
:string => { :name => "varchar", :limit => 255 },
|
117
104
|
:text => { :name => "text" },
|
118
105
|
:integer => { :name => "integer" },
|
@@ -145,44 +132,30 @@ module ActiveRecord
|
|
145
132
|
catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
|
146
133
|
end
|
147
134
|
|
148
|
-
def
|
149
|
-
|
135
|
+
def update_sql(sql, name = nil) #:nodoc:
|
136
|
+
super
|
150
137
|
@connection.changes
|
151
138
|
end
|
152
139
|
|
153
|
-
def
|
140
|
+
def delete_sql(sql, name = nil) #:nodoc:
|
154
141
|
sql += " WHERE 1=1" unless sql =~ /WHERE/i
|
155
|
-
|
156
|
-
@connection.changes
|
142
|
+
super sql, name
|
157
143
|
end
|
158
144
|
|
159
|
-
def
|
160
|
-
|
161
|
-
id_value || @connection.last_insert_row_id
|
145
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
146
|
+
super || @connection.last_insert_row_id
|
162
147
|
end
|
163
148
|
|
164
|
-
def
|
149
|
+
def select_rows(sql, name = nil)
|
165
150
|
execute(sql, name).map do |row|
|
166
|
-
|
167
|
-
row.each_key do |key|
|
168
|
-
if key.is_a?(String)
|
169
|
-
record[key.sub(/^\w+\./, '')] = row[key]
|
170
|
-
end
|
171
|
-
end
|
172
|
-
record
|
151
|
+
(0...(row.size / 2)).map { |i| row[i] }
|
173
152
|
end
|
174
153
|
end
|
175
154
|
|
176
|
-
def select_one(sql, name = nil) #:nodoc:
|
177
|
-
result = select_all(sql, name)
|
178
|
-
result.nil? ? nil : result.first
|
179
|
-
end
|
180
|
-
|
181
|
-
|
182
155
|
def begin_db_transaction #:nodoc:
|
183
156
|
catch_schema_changes { @connection.transaction }
|
184
157
|
end
|
185
|
-
|
158
|
+
|
186
159
|
def commit_db_transaction #:nodoc:
|
187
160
|
catch_schema_changes { @connection.commit }
|
188
161
|
end
|
@@ -201,7 +174,13 @@ module ActiveRecord
|
|
201
174
|
# SCHEMA STATEMENTS ========================================
|
202
175
|
|
203
176
|
def tables(name = nil) #:nodoc:
|
204
|
-
|
177
|
+
sql = <<-SQL
|
178
|
+
SELECT name
|
179
|
+
FROM sqlite_master
|
180
|
+
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
|
181
|
+
SQL
|
182
|
+
|
183
|
+
execute(sql, name).map do |row|
|
205
184
|
row[0]
|
206
185
|
end
|
207
186
|
end
|
@@ -229,7 +208,7 @@ module ActiveRecord
|
|
229
208
|
def remove_index(table_name, options={}) #:nodoc:
|
230
209
|
execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
|
231
210
|
end
|
232
|
-
|
211
|
+
|
233
212
|
def rename_table(name, new_name)
|
234
213
|
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
235
214
|
end
|
@@ -239,13 +218,13 @@ module ActiveRecord
|
|
239
218
|
# See last paragraph on http://www.sqlite.org/lang_altertable.html
|
240
219
|
execute "VACUUM"
|
241
220
|
end
|
242
|
-
|
221
|
+
|
243
222
|
def remove_column(table_name, column_name) #:nodoc:
|
244
223
|
alter_table(table_name) do |definition|
|
245
224
|
definition.columns.delete(definition[column_name])
|
246
225
|
end
|
247
226
|
end
|
248
|
-
|
227
|
+
|
249
228
|
def change_column_default(table_name, column_name, default) #:nodoc:
|
250
229
|
alter_table(table_name) do |definition|
|
251
230
|
definition[column_name].default = default
|
@@ -259,60 +238,78 @@ module ActiveRecord
|
|
259
238
|
self.type = type
|
260
239
|
self.limit = options[:limit] if options.include?(:limit)
|
261
240
|
self.default = options[:default] if include_default
|
241
|
+
self.null = options[:null] if options.include?(:null)
|
262
242
|
end
|
263
243
|
end
|
264
244
|
end
|
265
245
|
|
266
246
|
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
267
|
-
alter_table(table_name, :rename => {column_name => new_column_name})
|
247
|
+
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
|
248
|
+
end
|
249
|
+
|
250
|
+
def empty_insert_statement(table_name)
|
251
|
+
"INSERT INTO #{table_name} VALUES(NULL)"
|
268
252
|
end
|
269
|
-
|
270
253
|
|
271
254
|
protected
|
255
|
+
def select(sql, name = nil) #:nodoc:
|
256
|
+
execute(sql, name).map do |row|
|
257
|
+
record = {}
|
258
|
+
row.each_key do |key|
|
259
|
+
if key.is_a?(String)
|
260
|
+
record[key.sub(/^\w+\./, '')] = row[key]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
record
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
272
267
|
def table_structure(table_name)
|
273
268
|
returning structure = execute("PRAGMA table_info(#{table_name})") do
|
274
|
-
raise
|
269
|
+
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
275
270
|
end
|
276
271
|
end
|
277
|
-
|
272
|
+
|
278
273
|
def alter_table(table_name, options = {}) #:nodoc:
|
279
274
|
altered_table_name = "altered_#{table_name}"
|
280
275
|
caller = lambda {|definition| yield definition if block_given?}
|
281
276
|
|
282
277
|
transaction do
|
283
|
-
move_table(table_name, altered_table_name,
|
278
|
+
move_table(table_name, altered_table_name,
|
284
279
|
options.merge(:temporary => true))
|
285
280
|
move_table(altered_table_name, table_name, &caller)
|
286
281
|
end
|
287
282
|
end
|
288
|
-
|
283
|
+
|
289
284
|
def move_table(from, to, options = {}, &block) #:nodoc:
|
290
285
|
copy_table(from, to, options, &block)
|
291
286
|
drop_table(from)
|
292
287
|
end
|
293
|
-
|
288
|
+
|
294
289
|
def copy_table(from, to, options = {}) #:nodoc:
|
295
|
-
|
290
|
+
options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?)
|
291
|
+
create_table(to, options) do |definition|
|
292
|
+
@definition = definition
|
296
293
|
columns(from).each do |column|
|
297
294
|
column_name = options[:rename] ?
|
298
295
|
(options[:rename][column.name] ||
|
299
296
|
options[:rename][column.name.to_sym] ||
|
300
297
|
column.name) : column.name
|
301
|
-
|
302
|
-
@definition.column(column_name, column.type,
|
298
|
+
|
299
|
+
@definition.column(column_name, column.type,
|
303
300
|
:limit => column.limit, :default => column.default,
|
304
301
|
:null => column.null)
|
305
302
|
end
|
306
|
-
@definition.primary_key(primary_key(from))
|
303
|
+
@definition.primary_key(primary_key(from)) if primary_key(from)
|
307
304
|
yield @definition if block_given?
|
308
305
|
end
|
309
|
-
|
306
|
+
|
310
307
|
copy_table_indexes(from, to)
|
311
|
-
copy_table_contents(from, to,
|
312
|
-
@definition.columns.map {|column| column.name},
|
308
|
+
copy_table_contents(from, to,
|
309
|
+
@definition.columns.map {|column| column.name},
|
313
310
|
options[:rename] || {})
|
314
311
|
end
|
315
|
-
|
312
|
+
|
316
313
|
def copy_table_indexes(from, to) #:nodoc:
|
317
314
|
indexes(from).each do |index|
|
318
315
|
name = index.name
|
@@ -321,27 +318,29 @@ module ActiveRecord
|
|
321
318
|
elsif from == "altered_#{to}"
|
322
319
|
name = name[5..-1]
|
323
320
|
end
|
324
|
-
|
321
|
+
|
325
322
|
# index name can't be the same
|
326
323
|
opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
|
327
324
|
opts[:unique] = true if index.unique
|
328
325
|
add_index(to, index.columns, opts)
|
329
326
|
end
|
330
327
|
end
|
331
|
-
|
328
|
+
|
332
329
|
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
|
333
330
|
column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
|
334
331
|
rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
|
335
332
|
from_columns = columns(from).collect {|col| col.name}
|
336
333
|
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
334
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
|
335
|
+
|
337
336
|
@connection.execute "SELECT * FROM #{from}" do |row|
|
338
|
-
sql = "INSERT INTO #{to} (
|
337
|
+
sql = "INSERT INTO #{to} (#{quoted_columns}) VALUES ("
|
339
338
|
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
|
340
339
|
sql << ')'
|
341
340
|
@connection.execute sql
|
342
341
|
end
|
343
342
|
end
|
344
|
-
|
343
|
+
|
345
344
|
def catch_schema_changes
|
346
345
|
return yield
|
347
346
|
rescue ActiveRecord::StatementInvalid => exception
|
@@ -352,49 +351,34 @@ module ActiveRecord
|
|
352
351
|
raise
|
353
352
|
end
|
354
353
|
end
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
354
|
+
|
355
|
+
def sqlite_version
|
356
|
+
@sqlite_version ||= select_value('select sqlite_version(*)')
|
357
|
+
end
|
358
|
+
|
359
|
+
def default_primary_key_type
|
360
|
+
if supports_autoincrement?
|
361
|
+
'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
|
362
|
+
else
|
363
|
+
'INTEGER PRIMARY KEY NOT NULL'.freeze
|
364
|
+
end
|
361
365
|
end
|
362
|
-
end
|
363
366
|
end
|
364
367
|
|
365
368
|
class SQLite2Adapter < SQLiteAdapter # :nodoc:
|
366
|
-
|
367
|
-
|
368
|
-
# select COUNT(DISTINCT ArtistID) from CDs;
|
369
|
-
#
|
370
|
-
# In order to get the number of artists we execute the following statement
|
371
|
-
#
|
372
|
-
# SELECT COUNT(ArtistID) FROM (SELECT DISTINCT ArtistID FROM CDs);
|
373
|
-
def execute(sql, name = nil) #:nodoc:
|
374
|
-
super(rewrite_count_distinct_queries(sql), name)
|
375
|
-
end
|
376
|
-
|
377
|
-
def rewrite_count_distinct_queries(sql)
|
378
|
-
if sql =~ /count\(distinct ([^\)]+)\)( AS \w+)? (.*)/i
|
379
|
-
distinct_column = $1
|
380
|
-
distinct_query = $3
|
381
|
-
column_name = distinct_column.split('.').last
|
382
|
-
"SELECT COUNT(#{column_name}) FROM (SELECT DISTINCT #{distinct_column} #{distinct_query})"
|
383
|
-
else
|
384
|
-
sql
|
385
|
-
end
|
369
|
+
def supports_count_distinct? #:nodoc:
|
370
|
+
false
|
386
371
|
end
|
387
|
-
|
372
|
+
|
388
373
|
def rename_table(name, new_name)
|
389
374
|
move_table(name, new_name)
|
390
375
|
end
|
391
|
-
|
376
|
+
|
392
377
|
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
393
378
|
alter_table(table_name) do |definition|
|
394
379
|
definition.column(column_name, type, options)
|
395
380
|
end
|
396
381
|
end
|
397
|
-
|
398
382
|
end
|
399
383
|
|
400
384
|
class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
|
@@ -12,7 +12,7 @@ end
|
|
12
12
|
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
|
13
13
|
end
|
14
14
|
|
15
|
-
# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3
|
15
|
+
# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavors:
|
16
16
|
#
|
17
17
|
# 1. YAML fixtures
|
18
18
|
# 2. CSV fixtures
|
@@ -21,7 +21,7 @@ end
|
|
21
21
|
# = YAML fixtures
|
22
22
|
#
|
23
23
|
# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
|
24
|
-
# in a non-verbose,
|
24
|
+
# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
|
25
25
|
#
|
26
26
|
# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
|
27
27
|
# by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
|
@@ -82,7 +82,7 @@ end
|
|
82
82
|
#
|
83
83
|
# = Single-file fixtures
|
84
84
|
#
|
85
|
-
# This type of
|
85
|
+
# This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
|
86
86
|
# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
|
87
87
|
# appointed by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
|
88
88
|
# put your files in <your-rails-app>/test/fixtures/<your-model-name>/ -- like <your-rails-app>/test/fixtures/web_sites/ for the WebSite
|
@@ -106,7 +106,7 @@ end
|
|
106
106
|
# = Using Fixtures
|
107
107
|
#
|
108
108
|
# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
|
109
|
-
# fixtures, but first let's take a look at a sample unit test
|
109
|
+
# fixtures, but first let's take a look at a sample unit test:
|
110
110
|
#
|
111
111
|
# require 'web_site'
|
112
112
|
#
|
@@ -124,8 +124,8 @@ end
|
|
124
124
|
# fixtures :web_sites # add more by separating the symbols with commas
|
125
125
|
# ...
|
126
126
|
#
|
127
|
-
# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here
|
128
|
-
# the testing environment to automatically load the appropriate fixtures into the database before each test.
|
127
|
+
# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here though), we trigger
|
128
|
+
# the testing environment to automatically load the appropriate fixtures into the database before each test.
|
129
129
|
# To ensure consistent data, the environment deletes the fixtures before running the load.
|
130
130
|
#
|
131
131
|
# In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
|
@@ -151,7 +151,7 @@ end
|
|
151
151
|
# self.use_instantiated_fixtures = false
|
152
152
|
#
|
153
153
|
# - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
|
154
|
-
# self.use_instantiated_fixtures = :no_instances
|
154
|
+
# self.use_instantiated_fixtures = :no_instances
|
155
155
|
#
|
156
156
|
# Even if auto-instantiated fixtures are disabled, you can still access them
|
157
157
|
# by name via special dynamic methods. Each method has the same name as the
|
@@ -183,7 +183,7 @@ end
|
|
183
183
|
#
|
184
184
|
# = Transactional fixtures
|
185
185
|
#
|
186
|
-
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
|
186
|
+
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
|
187
187
|
# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
|
188
188
|
#
|
189
189
|
# class FooTest < Test::Unit::TestCase
|
@@ -203,22 +203,269 @@ end
|
|
203
203
|
# end
|
204
204
|
# end
|
205
205
|
#
|
206
|
-
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
|
206
|
+
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
|
207
207
|
# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
|
208
208
|
#
|
209
|
-
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
|
209
|
+
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
|
210
210
|
# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
|
211
211
|
#
|
212
|
-
# When *not* to use transactional fixtures:
|
213
|
-
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
|
214
|
-
# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
|
215
|
-
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress.
|
216
|
-
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
|
212
|
+
# When *not* to use transactional fixtures:
|
213
|
+
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
|
214
|
+
# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
|
215
|
+
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
|
216
|
+
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
|
217
217
|
# Use InnoDB, MaxDB, or NDB instead.
|
218
|
+
#
|
219
|
+
# = Advanced YAML Fixtures
|
220
|
+
#
|
221
|
+
# YAML fixtures that don't specify an ID get some extra features:
|
222
|
+
#
|
223
|
+
# * Stable, autogenerated ID's
|
224
|
+
# * Label references for associations (belongs_to, has_one, has_many)
|
225
|
+
# * HABTM associations as inline lists
|
226
|
+
# * Autofilled timestamp columns
|
227
|
+
# * Fixture label interpolation
|
228
|
+
# * Support for YAML defaults
|
229
|
+
#
|
230
|
+
# == Stable, autogenerated ID's
|
231
|
+
#
|
232
|
+
# Here, have a monkey fixture:
|
233
|
+
#
|
234
|
+
# george:
|
235
|
+
# id: 1
|
236
|
+
# name: George the Monkey
|
237
|
+
#
|
238
|
+
# reginald:
|
239
|
+
# id: 2
|
240
|
+
# name: Reginald the Pirate
|
241
|
+
#
|
242
|
+
# Each of these fixtures has two unique identifiers: one for the database
|
243
|
+
# and one for the humans. Why don't we generate the primary key instead?
|
244
|
+
# Hashing each fixture's label yields a consistent ID:
|
245
|
+
#
|
246
|
+
# george: # generated id: 503576764
|
247
|
+
# name: George the Monkey
|
248
|
+
#
|
249
|
+
# reginald: # generated id: 324201669
|
250
|
+
# name: Reginald the Pirate
|
251
|
+
#
|
252
|
+
# ActiveRecord looks at the fixture's model class, discovers the correct
|
253
|
+
# primary key, and generates it right before inserting the fixture
|
254
|
+
# into the database.
|
255
|
+
#
|
256
|
+
# The generated ID for a given label is constant, so we can discover
|
257
|
+
# any fixture's ID without loading anything, as long as we know the label.
|
258
|
+
#
|
259
|
+
# == Label references for associations (belongs_to, has_one, has_many)
|
260
|
+
#
|
261
|
+
# Specifying foreign keys in fixtures can be very fragile, not to
|
262
|
+
# mention difficult to read. Since ActiveRecord can figure out the ID of
|
263
|
+
# any fixture from its label, you can specify FK's by label instead of ID.
|
264
|
+
#
|
265
|
+
# === belongs_to
|
266
|
+
#
|
267
|
+
# Let's break out some more monkeys and pirates.
|
268
|
+
#
|
269
|
+
# ### in pirates.yml
|
270
|
+
#
|
271
|
+
# reginald:
|
272
|
+
# id: 1
|
273
|
+
# name: Reginald the Pirate
|
274
|
+
# monkey_id: 1
|
275
|
+
#
|
276
|
+
# ### in monkeys.yml
|
277
|
+
#
|
278
|
+
# george:
|
279
|
+
# id: 1
|
280
|
+
# name: George the Monkey
|
281
|
+
# pirate_id: 1
|
282
|
+
#
|
283
|
+
# Add a few more monkeys and pirates and break this into multiple files,
|
284
|
+
# and it gets pretty hard to keep track of what's going on. Let's
|
285
|
+
# use labels instead of ID's:
|
286
|
+
#
|
287
|
+
# ### in pirates.yml
|
288
|
+
#
|
289
|
+
# reginald:
|
290
|
+
# name: Reginald the Pirate
|
291
|
+
# monkey: george
|
292
|
+
#
|
293
|
+
# ### in monkeys.yml
|
294
|
+
#
|
295
|
+
# george:
|
296
|
+
# name: George the Monkey
|
297
|
+
# pirate: reginald
|
298
|
+
#
|
299
|
+
# Pow! All is made clear. ActiveRecord reflects on the fixture's model class,
|
300
|
+
# finds all the +belongs_to+ associations, and allows you to specify
|
301
|
+
# a target *label* for the *association* (monkey: george) rather than
|
302
|
+
# a target *id* for the *FK* (monkey_id: 1).
|
303
|
+
#
|
304
|
+
# ==== Polymorphic belongs_to
|
305
|
+
#
|
306
|
+
# Supporting polymorphic relationships is a little bit more complicated, since
|
307
|
+
# ActiveRecord needs to know what type your association is pointing at. Something
|
308
|
+
# like this should look familiar:
|
309
|
+
#
|
310
|
+
# ### in fruit.rb
|
311
|
+
#
|
312
|
+
# belongs_to :eater, :polymorphic => true
|
313
|
+
#
|
314
|
+
# ### in fruits.yml
|
315
|
+
#
|
316
|
+
# apple:
|
317
|
+
# id: 1
|
318
|
+
# name: apple
|
319
|
+
# eater_id: 1
|
320
|
+
# eater_type: Monkey
|
321
|
+
#
|
322
|
+
# Can we do better? You bet!
|
323
|
+
#
|
324
|
+
# apple:
|
325
|
+
# eater: george (Monkey)
|
326
|
+
#
|
327
|
+
# Just provide the polymorphic target type and ActiveRecord will take care of the rest.
|
328
|
+
#
|
329
|
+
# === has_and_belongs_to_many
|
330
|
+
#
|
331
|
+
# Time to give our monkey some fruit.
|
332
|
+
#
|
333
|
+
# ### in monkeys.yml
|
334
|
+
#
|
335
|
+
# george:
|
336
|
+
# id: 1
|
337
|
+
# name: George the Monkey
|
338
|
+
# pirate_id: 1
|
339
|
+
#
|
340
|
+
# ### in fruits.yml
|
341
|
+
#
|
342
|
+
# apple:
|
343
|
+
# id: 1
|
344
|
+
# name: apple
|
345
|
+
#
|
346
|
+
# orange:
|
347
|
+
# id: 2
|
348
|
+
# name: orange
|
349
|
+
#
|
350
|
+
# grape:
|
351
|
+
# id: 3
|
352
|
+
# name: grape
|
353
|
+
#
|
354
|
+
# ### in fruits_monkeys.yml
|
355
|
+
#
|
356
|
+
# apple_george:
|
357
|
+
# fruit_id: 1
|
358
|
+
# monkey_id: 1
|
359
|
+
#
|
360
|
+
# orange_george:
|
361
|
+
# fruit_id: 2
|
362
|
+
# monkey_id: 1
|
363
|
+
#
|
364
|
+
# grape_george:
|
365
|
+
# fruit_id: 3
|
366
|
+
# monkey_id: 1
|
367
|
+
#
|
368
|
+
# Let's make the HABTM fixture go away.
|
369
|
+
#
|
370
|
+
# ### in monkeys.yml
|
371
|
+
#
|
372
|
+
# george:
|
373
|
+
# name: George the Monkey
|
374
|
+
# pirate: reginald
|
375
|
+
# fruits: apple, orange, grape
|
376
|
+
#
|
377
|
+
# ### in fruits.yml
|
378
|
+
#
|
379
|
+
# apple:
|
380
|
+
# name: apple
|
381
|
+
#
|
382
|
+
# orange:
|
383
|
+
# name: orange
|
384
|
+
#
|
385
|
+
# grape:
|
386
|
+
# name: grape
|
387
|
+
#
|
388
|
+
# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
|
389
|
+
# on George's fixture, but we could've just as easily specified a list
|
390
|
+
# of monkeys on each fruit. As with +belongs_to+, ActiveRecord reflects on
|
391
|
+
# the fixture's model class and discovers the +has_and_belongs_to_many+
|
392
|
+
# associations.
|
393
|
+
#
|
394
|
+
# == Autofilled timestamp columns
|
395
|
+
#
|
396
|
+
# If your table/model specifies any of ActiveRecord's
|
397
|
+
# standard timestamp columns (created_at, created_on, updated_at, updated_on),
|
398
|
+
# they will automatically be set to Time.now.
|
399
|
+
#
|
400
|
+
# If you've set specific values, they'll be left alone.
|
401
|
+
#
|
402
|
+
# == Fixture label interpolation
|
403
|
+
#
|
404
|
+
# The label of the current fixture is always available as a column value:
|
405
|
+
#
|
406
|
+
# geeksomnia:
|
407
|
+
# name: Geeksomnia's Account
|
408
|
+
# subdomain: $LABEL
|
409
|
+
#
|
410
|
+
# Also, sometimes (like when porting older join table fixtures) you'll need
|
411
|
+
# to be able to get ahold of the identifier for a given label. ERB
|
412
|
+
# to the rescue:
|
413
|
+
#
|
414
|
+
# george_reginald:
|
415
|
+
# monkey_id: <%= Fixtures.identify(:reginald) %>
|
416
|
+
# pirate_id: <%= Fixtures.identify(:george) %>
|
417
|
+
#
|
418
|
+
# == Support for YAML defaults
|
419
|
+
#
|
420
|
+
# You probably already know how to use YAML to set and reuse defaults in
|
421
|
+
# your +database.yml+ file,. You can use the same technique in your fixtures:
|
422
|
+
#
|
423
|
+
# DEFAULTS: &DEFAULTS
|
424
|
+
# created_on: <%= 3.weeks.ago.to_s(:db) %>
|
425
|
+
#
|
426
|
+
# first:
|
427
|
+
# name: Smurf
|
428
|
+
# <<: *DEFAULTS
|
429
|
+
#
|
430
|
+
# second:
|
431
|
+
# name: Fraggle
|
432
|
+
# <<: *DEFAULTS
|
433
|
+
#
|
434
|
+
# Any fixture labeled "DEFAULTS" is safely ignored.
|
435
|
+
|
218
436
|
class Fixtures < YAML::Omap
|
219
437
|
DEFAULT_FILTER_RE = /\.ya?ml$/
|
220
438
|
|
221
|
-
|
439
|
+
@@all_cached_fixtures = {}
|
440
|
+
|
441
|
+
def self.reset_cache(connection = nil)
|
442
|
+
connection ||= ActiveRecord::Base.connection
|
443
|
+
@@all_cached_fixtures[connection.object_id] = {}
|
444
|
+
end
|
445
|
+
|
446
|
+
def self.cache_for_connection(connection)
|
447
|
+
@@all_cached_fixtures[connection.object_id] ||= {}
|
448
|
+
@@all_cached_fixtures[connection.object_id]
|
449
|
+
end
|
450
|
+
|
451
|
+
def self.fixture_is_cached?(connection, table_name)
|
452
|
+
cache_for_connection(connection)[table_name]
|
453
|
+
end
|
454
|
+
|
455
|
+
def self.cached_fixtures(connection, keys_to_fetch = nil)
|
456
|
+
if keys_to_fetch
|
457
|
+
fixtures = cache_for_connection(connection).values_at(*keys_to_fetch)
|
458
|
+
else
|
459
|
+
fixtures = cache_for_connection(connection).values
|
460
|
+
end
|
461
|
+
fixtures.size > 1 ? fixtures : fixtures.first
|
462
|
+
end
|
463
|
+
|
464
|
+
def self.cache_fixtures(connection, fixtures)
|
465
|
+
cache_for_connection(connection).update(fixtures.index_by(&:table_name))
|
466
|
+
end
|
467
|
+
|
468
|
+
def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
|
222
469
|
object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
|
223
470
|
if load_instances
|
224
471
|
ActiveRecord::Base.silence do
|
@@ -233,47 +480,63 @@ class Fixtures < YAML::Omap
|
|
233
480
|
end
|
234
481
|
end
|
235
482
|
|
236
|
-
def self.instantiate_all_loaded_fixtures(object, load_instances=true)
|
483
|
+
def self.instantiate_all_loaded_fixtures(object, load_instances = true)
|
237
484
|
all_loaded_fixtures.each do |table_name, fixtures|
|
238
485
|
Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
|
239
486
|
end
|
240
487
|
end
|
241
|
-
|
488
|
+
|
242
489
|
cattr_accessor :all_loaded_fixtures
|
243
490
|
self.all_loaded_fixtures = {}
|
244
491
|
|
245
492
|
def self.create_fixtures(fixtures_directory, table_names, class_names = {})
|
246
493
|
table_names = [table_names].flatten.map { |n| n.to_s }
|
247
|
-
connection
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
494
|
+
connection = block_given? ? yield : ActiveRecord::Base.connection
|
495
|
+
|
496
|
+
table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
|
497
|
+
|
498
|
+
unless table_names_to_fetch.empty?
|
499
|
+
ActiveRecord::Base.silence do
|
500
|
+
connection.disable_referential_integrity do
|
501
|
+
fixtures_map = {}
|
502
|
+
|
503
|
+
fixtures = table_names_to_fetch.map do |table_name|
|
504
|
+
fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
|
505
|
+
end
|
506
|
+
|
507
|
+
all_loaded_fixtures.update(fixtures_map)
|
508
|
+
|
509
|
+
connection.transaction(Thread.current['open_transactions'].to_i == 0) do
|
510
|
+
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
|
511
|
+
fixtures.each { |fixture| fixture.insert_fixtures }
|
512
|
+
|
513
|
+
# Cap primary key sequences to max(pk).
|
514
|
+
if connection.respond_to?(:reset_pk_sequence!)
|
515
|
+
table_names.each do |table_name|
|
516
|
+
connection.reset_pk_sequence!(table_name)
|
517
|
+
end
|
518
|
+
end
|
263
519
|
end
|
520
|
+
|
521
|
+
cache_fixtures(connection, fixtures)
|
264
522
|
end
|
265
523
|
end
|
266
|
-
|
267
|
-
return fixtures.size > 1 ? fixtures : fixtures.first
|
268
524
|
end
|
525
|
+
cached_fixtures(connection, table_names)
|
269
526
|
end
|
270
527
|
|
528
|
+
# Returns a consistent identifier for +label+. This will always
|
529
|
+
# be a positive integer, and will always be the same for a given
|
530
|
+
# label, assuming the same OS, platform, and version of Ruby.
|
531
|
+
def self.identify(label)
|
532
|
+
label.to_s.hash.abs
|
533
|
+
end
|
271
534
|
|
272
535
|
attr_reader :table_name
|
273
536
|
|
274
537
|
def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
|
275
538
|
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
|
276
|
-
@class_name = class_name ||
|
539
|
+
@class_name = class_name ||
|
277
540
|
(ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
|
278
541
|
@table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
|
279
542
|
@table_name = class_name.table_name if class_name.respond_to?(:table_name)
|
@@ -282,63 +545,132 @@ class Fixtures < YAML::Omap
|
|
282
545
|
end
|
283
546
|
|
284
547
|
def delete_existing_fixtures
|
285
|
-
@connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete'
|
548
|
+
@connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
|
286
549
|
end
|
287
550
|
|
288
551
|
def insert_fixtures
|
289
|
-
|
290
|
-
|
552
|
+
now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
|
553
|
+
now = now.to_s(:db)
|
554
|
+
|
555
|
+
# allow a standard key to be used for doing defaults in YAML
|
556
|
+
delete(assoc("DEFAULTS"))
|
557
|
+
|
558
|
+
# track any join tables we need to insert later
|
559
|
+
habtm_fixtures = Hash.new do |h, habtm|
|
560
|
+
h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil)
|
291
561
|
end
|
292
|
-
end
|
293
562
|
|
294
|
-
|
563
|
+
each do |label, fixture|
|
564
|
+
row = fixture.to_hash
|
295
565
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
566
|
+
if model_class && model_class < ActiveRecord::Base
|
567
|
+
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
|
568
|
+
if model_class.record_timestamps
|
569
|
+
timestamp_column_names.each do |name|
|
570
|
+
row[name] = now unless row.key?(name)
|
571
|
+
end
|
302
572
|
end
|
303
|
-
yaml_string << IO.read(yaml_file_path)
|
304
573
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}"
|
574
|
+
# interpolate the fixture label
|
575
|
+
row.each do |key, value|
|
576
|
+
row[key] = label if value == "$LABEL"
|
309
577
|
end
|
310
578
|
|
311
|
-
if
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
579
|
+
# generate a primary key if necessary
|
580
|
+
if has_primary_key_column? && !row.include?(primary_key_name)
|
581
|
+
row[primary_key_name] = Fixtures.identify(label)
|
582
|
+
end
|
583
|
+
|
584
|
+
# If STI is used, find the correct subclass for association reflection
|
585
|
+
reflection_class =
|
586
|
+
if row.include?(inheritance_column_name)
|
587
|
+
row[inheritance_column_name].constantize rescue model_class
|
588
|
+
else
|
589
|
+
model_class
|
590
|
+
end
|
319
591
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
592
|
+
reflection_class.reflect_on_all_associations.each do |association|
|
593
|
+
case association.macro
|
594
|
+
when :belongs_to
|
595
|
+
# Do not replace association name with association foreign key if they are named the same
|
596
|
+
fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
|
597
|
+
|
598
|
+
if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
|
599
|
+
if association.options[:polymorphic]
|
600
|
+
if value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
|
601
|
+
target_type = $1
|
602
|
+
target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s
|
603
|
+
|
604
|
+
# support polymorphic belongs_to as "label (Type)"
|
605
|
+
row[target_type_name] = target_type
|
606
|
+
end
|
324
607
|
end
|
325
608
|
|
326
|
-
|
609
|
+
row[fk_name] = Fixtures.identify(value)
|
610
|
+
end
|
611
|
+
when :has_and_belongs_to_many
|
612
|
+
if (targets = row.delete(association.name.to_s))
|
613
|
+
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
|
614
|
+
join_fixtures = habtm_fixtures[association]
|
615
|
+
|
616
|
+
targets.each do |target|
|
617
|
+
join_fixtures["#{label}_#{target}"] = Fixture.new(
|
618
|
+
{ association.primary_key_name => row[primary_key_name],
|
619
|
+
association.association_foreign_key => Fixtures.identify(target) }, nil)
|
620
|
+
end
|
327
621
|
end
|
328
622
|
end
|
329
623
|
end
|
624
|
+
end
|
625
|
+
|
626
|
+
@connection.insert_fixture(fixture, @table_name)
|
627
|
+
end
|
628
|
+
|
629
|
+
# insert any HABTM join tables we discovered
|
630
|
+
habtm_fixtures.values.each do |fixture|
|
631
|
+
fixture.delete_existing_fixtures
|
632
|
+
fixture.insert_fixtures
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
private
|
637
|
+
class HabtmFixtures < ::Fixtures #:nodoc:
|
638
|
+
def read_fixture_files; end
|
639
|
+
end
|
640
|
+
|
641
|
+
def model_class
|
642
|
+
@model_class ||= @class_name.is_a?(Class) ?
|
643
|
+
@class_name : @class_name.constantize rescue nil
|
644
|
+
end
|
645
|
+
|
646
|
+
def primary_key_name
|
647
|
+
@primary_key_name ||= model_class && model_class.primary_key
|
648
|
+
end
|
649
|
+
|
650
|
+
def has_primary_key_column?
|
651
|
+
@has_primary_key_column ||= model_class && primary_key_name &&
|
652
|
+
model_class.columns.find { |c| c.name == primary_key_name }
|
653
|
+
end
|
654
|
+
|
655
|
+
def timestamp_column_names
|
656
|
+
@timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name|
|
657
|
+
column_names.include?(name)
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
def inheritance_column_name
|
662
|
+
@inheritance_column_name ||= model_class && model_class.inheritance_column
|
663
|
+
end
|
664
|
+
|
665
|
+
def column_names
|
666
|
+
@column_names ||= @connection.columns(@table_name).collect(&:name)
|
667
|
+
end
|
668
|
+
|
669
|
+
def read_fixture_files
|
670
|
+
if File.file?(yaml_file_path)
|
671
|
+
read_yaml_fixture_files
|
330
672
|
elsif File.file?(csv_file_path)
|
331
|
-
|
332
|
-
reader = CSV::Reader.create(erb_render(IO.read(csv_file_path)))
|
333
|
-
header = reader.shift
|
334
|
-
i = 0
|
335
|
-
reader.each do |row|
|
336
|
-
data = {}
|
337
|
-
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
|
338
|
-
self["#{Inflector::underscore(@class_name)}_#{i+=1}"]= Fixture.new(data, @class_name)
|
339
|
-
end
|
340
|
-
elsif File.file?(deprecated_yaml_file_path)
|
341
|
-
raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}"
|
673
|
+
read_csv_fixture_files
|
342
674
|
else
|
343
675
|
# Standard fixtures
|
344
676
|
Dir.entries(@fixture_path).each do |file|
|
@@ -350,12 +682,47 @@ class Fixtures < YAML::Omap
|
|
350
682
|
end
|
351
683
|
end
|
352
684
|
|
353
|
-
def
|
354
|
-
"
|
685
|
+
def read_yaml_fixture_files
|
686
|
+
yaml_string = ""
|
687
|
+
Dir["#{@fixture_path}/**/*.yml"].select { |f| test(?f, f) }.each do |subfixture_path|
|
688
|
+
yaml_string << IO.read(subfixture_path)
|
689
|
+
end
|
690
|
+
yaml_string << IO.read(yaml_file_path)
|
691
|
+
|
692
|
+
if yaml = parse_yaml_string(yaml_string)
|
693
|
+
# If the file is an ordered map, extract its children.
|
694
|
+
yaml_value =
|
695
|
+
if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
|
696
|
+
yaml.value
|
697
|
+
else
|
698
|
+
[yaml]
|
699
|
+
end
|
700
|
+
|
701
|
+
yaml_value.each do |fixture|
|
702
|
+
fixture.each do |name, data|
|
703
|
+
unless data
|
704
|
+
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
|
705
|
+
end
|
706
|
+
|
707
|
+
self[name] = Fixture.new(data, @class_name)
|
708
|
+
end
|
709
|
+
end
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
def read_csv_fixture_files
|
714
|
+
reader = CSV::Reader.create(erb_render(IO.read(csv_file_path)))
|
715
|
+
header = reader.shift
|
716
|
+
i = 0
|
717
|
+
reader.each do |row|
|
718
|
+
data = {}
|
719
|
+
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
|
720
|
+
self["#{Inflector::underscore(@class_name)}_#{i+=1}"]= Fixture.new(data, @class_name)
|
721
|
+
end
|
355
722
|
end
|
356
723
|
|
357
|
-
def
|
358
|
-
"#{@fixture_path}.
|
724
|
+
def yaml_file_path
|
725
|
+
"#{@fixture_path}.yml"
|
359
726
|
end
|
360
727
|
|
361
728
|
def csv_file_path
|
@@ -366,6 +733,12 @@ class Fixtures < YAML::Omap
|
|
366
733
|
File.basename(@fixture_path).split(".").first
|
367
734
|
end
|
368
735
|
|
736
|
+
def parse_yaml_string(fixture_content)
|
737
|
+
YAML::load(erb_render(fixture_content))
|
738
|
+
rescue => error
|
739
|
+
raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
|
740
|
+
end
|
741
|
+
|
369
742
|
def erb_render(fixture_content)
|
370
743
|
ERB.new(fixture_content).result
|
371
744
|
end
|
@@ -373,11 +746,15 @@ end
|
|
373
746
|
|
374
747
|
class Fixture #:nodoc:
|
375
748
|
include Enumerable
|
376
|
-
|
749
|
+
|
750
|
+
class FixtureError < StandardError #:nodoc:
|
377
751
|
end
|
378
|
-
|
752
|
+
|
753
|
+
class FormatError < FixtureError #:nodoc:
|
379
754
|
end
|
380
755
|
|
756
|
+
attr_reader :class_name
|
757
|
+
|
381
758
|
def initialize(fixture, class_name)
|
382
759
|
case fixture
|
383
760
|
when Hash, YAML::Omap
|
@@ -450,36 +827,40 @@ end
|
|
450
827
|
module Test #:nodoc:
|
451
828
|
module Unit #:nodoc:
|
452
829
|
class TestCase #:nodoc:
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
830
|
+
superclass_delegating_accessor :fixture_path
|
831
|
+
superclass_delegating_accessor :fixture_table_names
|
832
|
+
superclass_delegating_accessor :fixture_class_names
|
833
|
+
superclass_delegating_accessor :use_transactional_fixtures
|
834
|
+
superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
|
835
|
+
superclass_delegating_accessor :pre_loaded_fixtures
|
459
836
|
|
460
837
|
self.fixture_table_names = []
|
461
838
|
self.use_transactional_fixtures = false
|
462
839
|
self.use_instantiated_fixtures = true
|
463
840
|
self.pre_loaded_fixtures = false
|
464
|
-
|
465
|
-
self.fixture_class_names = {}
|
466
|
-
|
841
|
+
|
467
842
|
@@already_loaded_fixtures = {}
|
468
843
|
self.fixture_class_names = {}
|
469
|
-
|
844
|
+
|
470
845
|
def self.set_fixture_class(class_names = {})
|
471
846
|
self.fixture_class_names = self.fixture_class_names.merge(class_names)
|
472
847
|
end
|
473
|
-
|
848
|
+
|
474
849
|
def self.fixtures(*table_names)
|
475
|
-
|
850
|
+
if table_names.first == :all
|
851
|
+
table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
|
852
|
+
table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
|
853
|
+
else
|
854
|
+
table_names = table_names.flatten.map { |n| n.to_s }
|
855
|
+
end
|
856
|
+
|
476
857
|
self.fixture_table_names |= table_names
|
477
858
|
require_fixture_classes(table_names)
|
478
859
|
setup_fixture_accessors(table_names)
|
479
860
|
end
|
480
861
|
|
481
|
-
def self.require_fixture_classes(table_names=nil)
|
482
|
-
(table_names || fixture_table_names).each do |table_name|
|
862
|
+
def self.require_fixture_classes(table_names = nil)
|
863
|
+
(table_names || fixture_table_names).each do |table_name|
|
483
864
|
file_name = table_name.to_s
|
484
865
|
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
|
485
866
|
begin
|
@@ -490,18 +871,26 @@ module Test #:nodoc:
|
|
490
871
|
end
|
491
872
|
end
|
492
873
|
|
493
|
-
def self.setup_fixture_accessors(table_names=nil)
|
874
|
+
def self.setup_fixture_accessors(table_names = nil)
|
494
875
|
(table_names || fixture_table_names).each do |table_name|
|
495
|
-
table_name = table_name.to_s.tr('.','_')
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
876
|
+
table_name = table_name.to_s.tr('.', '_')
|
877
|
+
|
878
|
+
define_method(table_name) do |*fixtures|
|
879
|
+
force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
|
880
|
+
|
881
|
+
@fixture_cache[table_name] ||= {}
|
882
|
+
|
883
|
+
instances = fixtures.map do |fixture|
|
884
|
+
@fixture_cache[table_name].delete(fixture) if force_reload
|
885
|
+
|
886
|
+
if @loaded_fixtures[table_name][fixture.to_s]
|
887
|
+
@fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
|
888
|
+
else
|
889
|
+
raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
|
890
|
+
end
|
504
891
|
end
|
892
|
+
|
893
|
+
instances.size == 1 ? instances.first : instances
|
505
894
|
end
|
506
895
|
end
|
507
896
|
end
|
@@ -525,10 +914,10 @@ module Test #:nodoc:
|
|
525
914
|
return unless defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank?
|
526
915
|
|
527
916
|
if pre_loaded_fixtures && !use_transactional_fixtures
|
528
|
-
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
917
|
+
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
529
918
|
end
|
530
919
|
|
531
|
-
@fixture_cache =
|
920
|
+
@fixture_cache = {}
|
532
921
|
|
533
922
|
# Load fixtures once and begin transaction.
|
534
923
|
if use_transactional_fixtures?
|
@@ -540,9 +929,9 @@ module Test #:nodoc:
|
|
540
929
|
end
|
541
930
|
ActiveRecord::Base.send :increment_open_transactions
|
542
931
|
ActiveRecord::Base.connection.begin_db_transaction
|
543
|
-
|
544
932
|
# Load fixtures for every test.
|
545
933
|
else
|
934
|
+
Fixtures.reset_cache
|
546
935
|
@@already_loaded_fixtures[self.class] = nil
|
547
936
|
load_fixtures
|
548
937
|
end
|
@@ -550,12 +939,15 @@ module Test #:nodoc:
|
|
550
939
|
# Instantiate fixtures for every test if requested.
|
551
940
|
instantiate_fixtures if use_instantiated_fixtures
|
552
941
|
end
|
553
|
-
|
554
942
|
alias_method :setup, :setup_with_fixtures
|
555
943
|
|
556
944
|
def teardown_with_fixtures
|
557
945
|
return unless defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank?
|
558
946
|
|
947
|
+
unless use_transactional_fixtures?
|
948
|
+
Fixtures.reset_cache
|
949
|
+
end
|
950
|
+
|
559
951
|
# Rollback changes if a transaction is active.
|
560
952
|
if use_transactional_fixtures? && Thread.current['open_transactions'] != 0
|
561
953
|
ActiveRecord::Base.connection.rollback_db_transaction
|
@@ -563,7 +955,6 @@ module Test #:nodoc:
|
|
563
955
|
end
|
564
956
|
ActiveRecord::Base.verify_active_connections!
|
565
957
|
end
|
566
|
-
|
567
958
|
alias_method :teardown, :teardown_with_fixtures
|
568
959
|
|
569
960
|
def self.method_added(method)
|
@@ -623,6 +1014,5 @@ module Test #:nodoc:
|
|
623
1014
|
use_instantiated_fixtures != :no_instances
|
624
1015
|
end
|
625
1016
|
end
|
626
|
-
|
627
1017
|
end
|
628
1018
|
end
|