activerecord 3.1.12 → 3.2.0.rc1
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.md +6263 -103
- data/README.rdoc +2 -2
- data/examples/performance.rb +55 -31
- data/lib/active_record.rb +28 -2
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations.rb +82 -69
- data/lib/active_record/associations/association.rb +2 -37
- data/lib/active_record/associations/association_scope.rb +3 -30
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +55 -28
- data/lib/active_record/associations/collection_proxy.rb +1 -35
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +11 -8
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +3 -1
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +212 -32
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +69 -80
- data/lib/active_record/attribute_methods/serialization.rb +89 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
- data/lib/active_record/attribute_methods/write.rb +27 -5
- data/lib/active_record/autosave_association.rb +23 -8
- data/lib/active_record/base.rb +223 -1712
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
- data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +83 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +31 -76
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +1 -7
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +19 -11
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration.rb +38 -29
- data/lib/active_record/migration/command_recorder.rb +7 -7
- data/lib/active_record/model_schema.rb +362 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +51 -1
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +24 -28
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +133 -77
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation.rb +78 -35
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +27 -6
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +5 -4
- data/lib/active_record/relation/predicate_builder.rb +13 -16
- data/lib/active_record/relation/query_methods.rb +59 -4
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +5 -2
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +140 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +2 -44
- data/lib/active_record/session_store.rb +11 -11
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +16 -3
- data/lib/active_record/transactions.rb +5 -5
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +48 -38
- checksums.yaml +0 -7
- data/lib/active_record/named_scope.rb +0 -200
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
require 'active_support/core_ext/kernel/requires'
|
3
2
|
require 'active_record/connection_adapters/statement_pool'
|
4
3
|
require 'active_support/core_ext/string/encoding'
|
5
|
-
require 'arel/visitors/bind_visitor'
|
6
4
|
|
7
5
|
module ActiveRecord
|
8
6
|
module ConnectionAdapters #:nodoc:
|
@@ -86,25 +84,12 @@ module ActiveRecord
|
|
86
84
|
end
|
87
85
|
end
|
88
86
|
|
89
|
-
class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
|
90
|
-
include Arel::Visitors::BindVisitor
|
91
|
-
end
|
92
|
-
|
93
87
|
def initialize(connection, logger, config)
|
94
88
|
super(connection, logger)
|
95
89
|
@statements = StatementPool.new(@connection,
|
96
90
|
config.fetch(:statement_limit) { 1000 })
|
97
91
|
@config = config
|
98
|
-
|
99
|
-
|
100
|
-
def self.visitor_for(pool) # :nodoc:
|
101
|
-
config = pool.spec.config
|
102
|
-
|
103
|
-
if config.fetch(:prepared_statements) { true }
|
104
|
-
Arel::Visitors::SQLite.new pool
|
105
|
-
else
|
106
|
-
BindSubstitution.new pool
|
107
|
-
end
|
92
|
+
@visitor = Arel::Visitors::SQLite.new self
|
108
93
|
end
|
109
94
|
|
110
95
|
def adapter_name #:nodoc:
|
@@ -137,6 +122,11 @@ module ActiveRecord
|
|
137
122
|
true
|
138
123
|
end
|
139
124
|
|
125
|
+
# Returns true.
|
126
|
+
def supports_explain?
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
140
130
|
def requires_reloading?
|
141
131
|
true
|
142
132
|
end
|
@@ -169,6 +159,10 @@ module ActiveRecord
|
|
169
159
|
sqlite_version >= '3.1.0'
|
170
160
|
end
|
171
161
|
|
162
|
+
def supports_index_sort_order?
|
163
|
+
sqlite_version >= '3.3.0'
|
164
|
+
end
|
165
|
+
|
172
166
|
def native_database_types #:nodoc:
|
173
167
|
{
|
174
168
|
:primary_key => default_primary_key_type,
|
@@ -215,7 +209,7 @@ module ActiveRecord
|
|
215
209
|
|
216
210
|
value = super
|
217
211
|
if column.type == :string && value.encoding == Encoding::ASCII_8BIT
|
218
|
-
logger.error "Binary data inserted for `string` type on column `#{column.name}`"
|
212
|
+
@logger.error "Binary data inserted for `string` type on column `#{column.name}`"
|
219
213
|
value.encode! 'utf-8'
|
220
214
|
end
|
221
215
|
value
|
@@ -230,6 +224,25 @@ module ActiveRecord
|
|
230
224
|
|
231
225
|
# DATABASE STATEMENTS ======================================
|
232
226
|
|
227
|
+
def explain(arel, binds = [])
|
228
|
+
sql = "EXPLAIN QUERY PLAN #{to_sql(arel)}"
|
229
|
+
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
230
|
+
end
|
231
|
+
|
232
|
+
class ExplainPrettyPrinter
|
233
|
+
# Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
|
234
|
+
# the output of the SQLite shell:
|
235
|
+
#
|
236
|
+
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
|
237
|
+
# 0|1|1|SCAN TABLE posts (~100000 rows)
|
238
|
+
#
|
239
|
+
def pp(result) # :nodoc:
|
240
|
+
result.rows.map do |row|
|
241
|
+
row.join('|')
|
242
|
+
end.join("\n") + "\n"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
233
246
|
def exec_query(sql, name = nil, binds = [])
|
234
247
|
log(sql, name, binds) do
|
235
248
|
|
@@ -303,31 +316,36 @@ module ActiveRecord
|
|
303
316
|
end
|
304
317
|
|
305
318
|
def begin_db_transaction #:nodoc:
|
306
|
-
@connection.transaction
|
319
|
+
log('begin transaction',nil) { @connection.transaction }
|
307
320
|
end
|
308
321
|
|
309
322
|
def commit_db_transaction #:nodoc:
|
310
|
-
@connection.commit
|
323
|
+
log('commit transaction',nil) { @connection.commit }
|
311
324
|
end
|
312
325
|
|
313
326
|
def rollback_db_transaction #:nodoc:
|
314
|
-
@connection.rollback
|
327
|
+
log('rollback transaction',nil) { @connection.rollback }
|
315
328
|
end
|
316
329
|
|
317
330
|
# SCHEMA STATEMENTS ========================================
|
318
331
|
|
319
|
-
def tables(name = 'SCHEMA') #:nodoc:
|
332
|
+
def tables(name = 'SCHEMA', table_name = nil) #:nodoc:
|
320
333
|
sql = <<-SQL
|
321
334
|
SELECT name
|
322
335
|
FROM sqlite_master
|
323
336
|
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
|
324
337
|
SQL
|
338
|
+
sql << " AND name = #{quote_table_name(table_name)}" if table_name
|
325
339
|
|
326
340
|
exec_query(sql, name).map do |row|
|
327
341
|
row['name']
|
328
342
|
end
|
329
343
|
end
|
330
344
|
|
345
|
+
def table_exists?(name)
|
346
|
+
name && tables('SCHEMA', name).any?
|
347
|
+
end
|
348
|
+
|
331
349
|
# Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
|
332
350
|
def columns(table_name, name = nil) #:nodoc:
|
333
351
|
table_structure(table_name).map do |field|
|
@@ -393,7 +411,7 @@ module ActiveRecord
|
|
393
411
|
end
|
394
412
|
|
395
413
|
def remove_column(table_name, *column_names) #:nodoc:
|
396
|
-
raise ArgumentError.new("You must specify at least one column name.
|
414
|
+
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
397
415
|
column_names.flatten.each do |column_name|
|
398
416
|
alter_table(table_name) do |definition|
|
399
417
|
definition.columns.delete(definition[column_name])
|
@@ -425,6 +443,8 @@ module ActiveRecord
|
|
425
443
|
self.limit = options[:limit] if options.include?(:limit)
|
426
444
|
self.default = options[:default] if include_default
|
427
445
|
self.null = options[:null] if options.include?(:null)
|
446
|
+
self.precision = options[:precision] if options.include?(:precision)
|
447
|
+
self.scale = options[:scale] if options.include?(:scale)
|
428
448
|
end
|
429
449
|
end
|
430
450
|
end
|
@@ -479,6 +499,7 @@ module ActiveRecord
|
|
479
499
|
|
480
500
|
@definition.column(column_name, column.type,
|
481
501
|
:limit => column.limit, :default => column.default,
|
502
|
+
:precision => column.precision, :scale => column.scale,
|
482
503
|
:null => column.null)
|
483
504
|
end
|
484
505
|
@definition.primary_key(primary_key(from)) if primary_key(from)
|
@@ -2,7 +2,7 @@ module ActiveRecord
|
|
2
2
|
# = Active Record Counter Cache
|
3
3
|
module CounterCache
|
4
4
|
# Resets one or more counter caches to their correct value using an SQL
|
5
|
-
# count query.
|
5
|
+
# count query. This is useful when adding new counter caches, or if the
|
6
6
|
# counter has been corrupted or modified directly by SQL.
|
7
7
|
#
|
8
8
|
# ==== Parameters
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module DynamicMatchers
|
3
|
+
def respond_to?(method_id, include_private = false)
|
4
|
+
if match = DynamicFinderMatch.match(method_id)
|
5
|
+
return true if all_attributes_exists?(match.attribute_names)
|
6
|
+
elsif match = DynamicScopeMatch.match(method_id)
|
7
|
+
return true if all_attributes_exists?(match.attribute_names)
|
8
|
+
end
|
9
|
+
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
|
16
|
+
# <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
|
17
|
+
# section at the top of this file for more detailed information.
|
18
|
+
#
|
19
|
+
# It's even possible to use all the additional parameters to +find+. For example, the
|
20
|
+
# full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
|
21
|
+
#
|
22
|
+
# Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
|
23
|
+
# is first invoked, so that future attempts to use it do not run through method_missing.
|
24
|
+
def method_missing(method_id, *arguments, &block)
|
25
|
+
if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id))
|
26
|
+
attribute_names = match.attribute_names
|
27
|
+
super unless all_attributes_exists?(attribute_names)
|
28
|
+
if arguments.size < attribute_names.size
|
29
|
+
method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
|
30
|
+
backtrace = [method_trace] + caller
|
31
|
+
raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
|
32
|
+
end
|
33
|
+
if match.respond_to?(:scope?) && match.scope?
|
34
|
+
self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
35
|
+
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
36
|
+
attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
|
37
|
+
#
|
38
|
+
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
39
|
+
end # end
|
40
|
+
METHOD
|
41
|
+
send(method_id, *arguments)
|
42
|
+
elsif match.finder?
|
43
|
+
options = arguments.extract_options!
|
44
|
+
relation = options.any? ? scoped(options) : scoped
|
45
|
+
relation.send :find_by_attributes, match, attribute_names, *arguments, &block
|
46
|
+
elsif match.instantiator?
|
47
|
+
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
|
48
|
+
end
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Similar in purpose to +expand_hash_conditions_for_aggregates+.
|
55
|
+
def expand_attribute_names_for_aggregates(attribute_names)
|
56
|
+
attribute_names.map { |attribute_name|
|
57
|
+
unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
|
58
|
+
aggregate_mapping(aggregation).map do |field_attr, _|
|
59
|
+
field_attr.to_sym
|
60
|
+
end
|
61
|
+
else
|
62
|
+
attribute_name.to_sym
|
63
|
+
end
|
64
|
+
}.flatten
|
65
|
+
end
|
66
|
+
|
67
|
+
def all_attributes_exists?(attribute_names)
|
68
|
+
(expand_attribute_names_for_aggregates(attribute_names) -
|
69
|
+
column_methods_hash.keys).empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
def aggregate_mapping(reflection)
|
73
|
+
mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
|
74
|
+
mapping.first.is_a?(Array) ? mapping : [mapping]
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
data/lib/active_record/errors.rb
CHANGED
@@ -87,7 +87,7 @@ module ActiveRecord
|
|
87
87
|
#
|
88
88
|
# For example, in
|
89
89
|
#
|
90
|
-
# Location.
|
90
|
+
# Location.where("lat = ? AND lng = ?", 53.7362)
|
91
91
|
#
|
92
92
|
# two placeholders are given but only one variable to fill them.
|
93
93
|
class PreparedStatementInvalid < ActiveRecordError
|
@@ -99,6 +99,16 @@ module ActiveRecord
|
|
99
99
|
#
|
100
100
|
# Read more about optimistic locking in ActiveRecord::Locking module RDoc.
|
101
101
|
class StaleObjectError < ActiveRecordError
|
102
|
+
attr_reader :record, :attempted_action
|
103
|
+
|
104
|
+
def initialize(record, attempted_action)
|
105
|
+
@record = record
|
106
|
+
@attempted_action = attempted_action
|
107
|
+
end
|
108
|
+
|
109
|
+
def message
|
110
|
+
"Attempted to #{attempted_action} a stale object: #{record.class.name}"
|
111
|
+
end
|
102
112
|
end
|
103
113
|
|
104
114
|
# Raised when association is being configured improperly or
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Explain
|
5
|
+
def self.extended(base)
|
6
|
+
base.class_eval do
|
7
|
+
# If a query takes longer than these many seconds we log its query plan
|
8
|
+
# automatically. nil disables this feature.
|
9
|
+
class_attribute :auto_explain_threshold_in_seconds, :instance_writer => false
|
10
|
+
self.auto_explain_threshold_in_seconds = nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# If auto explain is enabled, this method triggers EXPLAIN logging for the
|
15
|
+
# queries triggered by the block if it takes more than the threshold as a
|
16
|
+
# whole. That is, the threshold is not checked against each individual
|
17
|
+
# query, but against the duration of the entire block. This approach is
|
18
|
+
# convenient for relations.
|
19
|
+
#
|
20
|
+
# The available_queries_for_explain thread variable collects the queries
|
21
|
+
# to be explained. If the value is nil, it means queries are not being
|
22
|
+
# currently collected. A false value indicates collecting is turned
|
23
|
+
# off. Otherwise it is an array of queries.
|
24
|
+
def logging_query_plan # :nodoc:
|
25
|
+
threshold = auto_explain_threshold_in_seconds
|
26
|
+
current = Thread.current
|
27
|
+
if threshold && current[:available_queries_for_explain].nil?
|
28
|
+
begin
|
29
|
+
queries = current[:available_queries_for_explain] = []
|
30
|
+
start = Time.now
|
31
|
+
result = yield
|
32
|
+
logger.warn(exec_explain(queries)) if Time.now - start > threshold
|
33
|
+
result
|
34
|
+
ensure
|
35
|
+
current[:available_queries_for_explain] = nil
|
36
|
+
end
|
37
|
+
else
|
38
|
+
yield
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Relation#explain needs to be able to collect the queries regardless of
|
43
|
+
# whether auto explain is enabled. This method serves that purpose.
|
44
|
+
def collecting_queries_for_explain # :nodoc:
|
45
|
+
current = Thread.current
|
46
|
+
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
|
47
|
+
return yield, current[:available_queries_for_explain]
|
48
|
+
ensure
|
49
|
+
# Note that the return value above does not depend on this assigment.
|
50
|
+
current[:available_queries_for_explain] = original
|
51
|
+
end
|
52
|
+
|
53
|
+
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
|
54
|
+
# Returns a formatted string ready to be logged.
|
55
|
+
def exec_explain(queries) # :nodoc:
|
56
|
+
queries && queries.map do |sql, bind|
|
57
|
+
[].tap do |msg|
|
58
|
+
msg << "EXPLAIN for: #{sql}"
|
59
|
+
unless bind.empty?
|
60
|
+
bind_msg = bind.map {|col, val| [col.name, val]}.inspect
|
61
|
+
msg.last << " #{bind_msg}"
|
62
|
+
end
|
63
|
+
msg << connection.explain(sql, bind)
|
64
|
+
end.join("\n")
|
65
|
+
end.join("\n")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Silences automatic EXPLAIN logging for the duration of the block.
|
69
|
+
#
|
70
|
+
# This has high priority, no EXPLAINs will be run even if downwards
|
71
|
+
# the threshold is set to 0.
|
72
|
+
#
|
73
|
+
# As the name of the method suggests this only applies to automatic
|
74
|
+
# EXPLAINs, manual calls to +ActiveRecord::Relation#explain+ run.
|
75
|
+
def silence_auto_explain
|
76
|
+
current = Thread.current
|
77
|
+
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
|
78
|
+
yield
|
79
|
+
ensure
|
80
|
+
current[:available_queries_for_explain] = original
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'active_support/notifications'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class ExplainSubscriber # :nodoc:
|
5
|
+
def call(*args)
|
6
|
+
if queries = Thread.current[:available_queries_for_explain]
|
7
|
+
payload = args.last
|
8
|
+
queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
|
13
|
+
# our own EXPLAINs now matter how loopingly beautiful that would be.
|
14
|
+
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
|
15
|
+
def ignore_payload?(payload)
|
16
|
+
payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name])
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveSupport::Notifications.subscribe("sql.active_record", new)
|
20
|
+
end
|
21
|
+
end
|
@@ -6,14 +6,13 @@ rescue LoadError
|
|
6
6
|
end
|
7
7
|
|
8
8
|
require 'yaml'
|
9
|
-
require 'csv'
|
10
9
|
require 'zlib'
|
11
10
|
require 'active_support/dependencies'
|
12
11
|
require 'active_support/core_ext/array/wrap'
|
13
12
|
require 'active_support/core_ext/object/blank'
|
14
13
|
require 'active_support/core_ext/logger'
|
15
14
|
require 'active_support/ordered_hash'
|
16
|
-
require '
|
15
|
+
require 'active_record/fixtures/file'
|
17
16
|
|
18
17
|
if defined? ActiveRecord
|
19
18
|
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
|
@@ -23,8 +22,6 @@ else
|
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
26
|
-
class FixturesFileNotFound < StandardError; end
|
27
|
-
|
28
25
|
module ActiveRecord
|
29
26
|
# \Fixtures are a way of organizing data that you want to test against; in short, sample data.
|
30
27
|
#
|
@@ -467,7 +464,7 @@ module ActiveRecord
|
|
467
464
|
connection,
|
468
465
|
table_name,
|
469
466
|
class_names[table_name.to_sym] || table_name.classify,
|
470
|
-
File.join(fixtures_directory, path))
|
467
|
+
::File.join(fixtures_directory, path))
|
471
468
|
end
|
472
469
|
|
473
470
|
all_loaded_fixtures.update(fixtures_map)
|
@@ -645,80 +642,25 @@ module ActiveRecord
|
|
645
642
|
end
|
646
643
|
|
647
644
|
def read_fixture_files
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
def read_yaml_fixture_files
|
658
|
-
yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
|
659
|
-
File.file?(f)
|
660
|
-
} + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
|
661
|
-
|
662
|
-
if yaml = parse_yaml_string(yaml_string)
|
663
|
-
# If the file is an ordered map, extract its children.
|
664
|
-
yaml_value =
|
665
|
-
if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
|
666
|
-
yaml.value
|
667
|
-
else
|
668
|
-
[yaml]
|
669
|
-
end
|
670
|
-
|
671
|
-
yaml_value.each do |fixture|
|
672
|
-
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
|
673
|
-
fixture.each do |name, data|
|
674
|
-
unless data
|
675
|
-
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
|
676
|
-
end
|
677
|
-
|
678
|
-
fixtures[name] = ActiveRecord::Fixture.new(data, model_class)
|
645
|
+
yaml_files = Dir["#{@fixture_path}/**/*.yml"].select { |f|
|
646
|
+
::File.file?(f)
|
647
|
+
} + [yaml_file_path]
|
648
|
+
|
649
|
+
yaml_files.each do |file|
|
650
|
+
Fixtures::File.open(file) do |fh|
|
651
|
+
fh.each do |name, row|
|
652
|
+
fixtures[name] = ActiveRecord::Fixture.new(row, model_class)
|
679
653
|
end
|
680
654
|
end
|
681
655
|
end
|
682
656
|
end
|
683
657
|
|
684
|
-
def read_csv_fixture_files
|
685
|
-
reader = CSV.parse(erb_render(IO.read(csv_file_path)))
|
686
|
-
header = reader.shift
|
687
|
-
i = 0
|
688
|
-
reader.each do |row|
|
689
|
-
data = {}
|
690
|
-
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
|
691
|
-
fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = ActiveRecord::Fixture.new(data, model_class)
|
692
|
-
end
|
693
|
-
end
|
694
|
-
deprecate :read_csv_fixture_files
|
695
|
-
|
696
658
|
def yaml_file_path
|
697
659
|
"#{@fixture_path}.yml"
|
698
660
|
end
|
699
661
|
|
700
|
-
def csv_file_path
|
701
|
-
@fixture_path + ".csv"
|
702
|
-
end
|
703
|
-
|
704
662
|
def yaml_fixtures_key(path)
|
705
|
-
File.basename(@fixture_path).split(".").first
|
706
|
-
end
|
707
|
-
|
708
|
-
RESCUE_ERRORS = [ ArgumentError ]
|
709
|
-
|
710
|
-
if defined?(Psych) && defined?(Psych::SyntaxError)
|
711
|
-
RESCUE_ERRORS << Psych::SyntaxError
|
712
|
-
end
|
713
|
-
|
714
|
-
def parse_yaml_string(fixture_content)
|
715
|
-
YAML::load(erb_render(fixture_content))
|
716
|
-
rescue *RESCUE_ERRORS => error
|
717
|
-
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}", error.backtrace
|
718
|
-
end
|
719
|
-
|
720
|
-
def erb_render(fixture_content)
|
721
|
-
ERB.new(fixture_content).result
|
663
|
+
::File.basename(@fixture_path).split(".").first
|
722
664
|
end
|
723
665
|
end
|
724
666
|
|
@@ -794,7 +736,7 @@ module ActiveRecord
|
|
794
736
|
|
795
737
|
def fixtures(*fixture_names)
|
796
738
|
if fixture_names.first == :all
|
797
|
-
fixture_names = Dir["#{fixture_path}/**/*.{yml
|
739
|
+
fixture_names = Dir["#{fixture_path}/**/*.{yml}"]
|
798
740
|
fixture_names.map! { |f| f[(fixture_path.size + 1)..-5] }
|
799
741
|
else
|
800
742
|
fixture_names = fixture_names.flatten.map { |n| n.to_s }
|
@@ -880,6 +822,7 @@ module ActiveRecord
|
|
880
822
|
end
|
881
823
|
|
882
824
|
@fixture_cache = {}
|
825
|
+
@fixture_connections = []
|
883
826
|
@@already_loaded_fixtures ||= {}
|
884
827
|
|
885
828
|
# Load fixtures once and begin transaction.
|
@@ -890,9 +833,12 @@ module ActiveRecord
|
|
890
833
|
@loaded_fixtures = load_fixtures
|
891
834
|
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
892
835
|
end
|
893
|
-
|
894
|
-
|
895
|
-
|
836
|
+
@fixture_connections = enlist_fixture_connections
|
837
|
+
@fixture_connections.each do |connection|
|
838
|
+
connection.increment_open_transactions
|
839
|
+
connection.transaction_joinable = false
|
840
|
+
connection.begin_db_transaction
|
841
|
+
end
|
896
842
|
# Load fixtures for every test.
|
897
843
|
else
|
898
844
|
ActiveRecord::Fixtures.reset_cache
|
@@ -912,13 +858,22 @@ module ActiveRecord
|
|
912
858
|
end
|
913
859
|
|
914
860
|
# Rollback changes if a transaction is active.
|
915
|
-
if run_in_transaction?
|
916
|
-
|
917
|
-
|
861
|
+
if run_in_transaction?
|
862
|
+
@fixture_connections.each do |connection|
|
863
|
+
if connection.open_transactions != 0
|
864
|
+
connection.rollback_db_transaction
|
865
|
+
connection.decrement_open_transactions
|
866
|
+
end
|
867
|
+
end
|
868
|
+
@fixture_connections.clear
|
918
869
|
end
|
919
870
|
ActiveRecord::Base.clear_active_connections!
|
920
871
|
end
|
921
872
|
|
873
|
+
def enlist_fixture_connections
|
874
|
+
ActiveRecord::Base.connection_handler.connection_pools.values.map(&:connection)
|
875
|
+
end
|
876
|
+
|
922
877
|
private
|
923
878
|
def load_fixtures
|
924
879
|
fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
|