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.

Files changed (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. 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
- parse_config!(config)
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 parse_config!(config)
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
- false
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 => "INTEGER PRIMARY KEY NOT NULL",
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 update(sql, name = nil) #:nodoc:
149
- execute(sql, name)
135
+ def update_sql(sql, name = nil) #:nodoc:
136
+ super
150
137
  @connection.changes
151
138
  end
152
139
 
153
- def delete(sql, name = nil) #:nodoc:
140
+ def delete_sql(sql, name = nil) #:nodoc:
154
141
  sql += " WHERE 1=1" unless sql =~ /WHERE/i
155
- execute(sql, name)
156
- @connection.changes
142
+ super sql, name
157
143
  end
158
144
 
159
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
160
- execute(sql, name = nil)
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 select_all(sql, name = nil) #:nodoc:
149
+ def select_rows(sql, name = nil)
165
150
  execute(sql, name).map do |row|
166
- record = {}
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
- execute("SELECT name FROM sqlite_master WHERE type = 'table'", name).map do |row|
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 ActiveRecord::StatementInvalid if structure.empty?
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
- create_table(to, options) do |@definition|
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} ("+columns*','+") VALUES ("
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
- end
356
-
357
- class SQLite3Adapter < SQLiteAdapter # :nodoc:
358
- def table_structure(table_name)
359
- returning structure = @connection.table_info(table_name) do
360
- raise ActiveRecord::StatementInvalid if structure.empty?
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
- # SQLite 2 does not support COUNT(DISTINCT) queries:
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 flavours:
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, humanly-readable format. It ships with Ruby 1.8.1+.
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 fixtures was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
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 found:
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 tho), we trigger
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
- def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)
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 = block_given? ? yield : ActiveRecord::Base.connection
248
- ActiveRecord::Base.silence do
249
- fixtures_map = {}
250
- fixtures = table_names.map do |table_name|
251
- 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))
252
- end
253
- all_loaded_fixtures.merge! fixtures_map
254
-
255
- connection.transaction(Thread.current['open_transactions'] == 0) do
256
- fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
257
- fixtures.each { |fixture| fixture.insert_fixtures }
258
-
259
- # Cap primary key sequences to max(pk).
260
- if connection.respond_to?(:reset_pk_sequence!)
261
- table_names.each do |table_name|
262
- connection.reset_pk_sequence!(table_name)
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
- values.each do |fixture|
290
- @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
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
- private
563
+ each do |label, fixture|
564
+ row = fixture.to_hash
295
565
 
296
- def read_fixture_files
297
- if File.file?(yaml_file_path)
298
- # YAML fixtures
299
- yaml_string = ""
300
- Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|
301
- yaml_string << IO.read(subfixture_path)
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
- begin
306
- yaml = YAML::load(erb_render(yaml_string))
307
- rescue Exception=>boom
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 yaml
312
- # If the file is an ordered map, extract its children.
313
- yaml_value =
314
- if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
315
- yaml.value
316
- else
317
- [yaml]
318
- end
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
- yaml_value.each do |fixture|
321
- fixture.each do |name, data|
322
- unless data
323
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
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
- self[name] = Fixture.new(data, @class_name)
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
- # CSV fixtures
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 yaml_file_path
354
- "#{@fixture_path}.yml"
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 deprecated_yaml_file_path
358
- "#{@fixture_path}.yaml"
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
- class FixtureError < StandardError#:nodoc:
749
+
750
+ class FixtureError < StandardError #:nodoc:
377
751
  end
378
- class FormatError < FixtureError#:nodoc:
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
- cattr_accessor :fixture_path
454
- class_inheritable_accessor :fixture_table_names
455
- class_inheritable_accessor :fixture_class_names
456
- class_inheritable_accessor :use_transactional_fixtures
457
- class_inheritable_accessor :use_instantiated_fixtures # true, false, or :no_instances
458
- class_inheritable_accessor :pre_loaded_fixtures
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
- table_names = table_names.flatten.map { |n| n.to_s }
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
- define_method(table_name) do |fixture, *optionals|
497
- force_reload = optionals.shift
498
- @fixture_cache[table_name] ||= Hash.new
499
- @fixture_cache[table_name][fixture] = nil if force_reload
500
- if @loaded_fixtures[table_name][fixture.to_s]
501
- @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
502
- else
503
- raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
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 = Hash.new
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