activerecord 3.2.8 → 3.2.9.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 +154 -6558
- data/examples/performance.rb +32 -37
- data/lib/active_record/associations.rb +8 -5
- data/lib/active_record/associations/collection_association.rb +5 -3
- data/lib/active_record/associations/has_many_through_association.rb +14 -0
- data/lib/active_record/associations/has_one_association.rb +16 -14
- data/lib/active_record/associations/preloader.rb +14 -10
- data/lib/active_record/associations/preloader/association.rb +1 -3
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +14 -5
- data/lib/active_record/base.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +10 -4
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/column.rb +24 -9
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -33
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -3
- data/lib/active_record/counter_cache.rb +5 -1
- data/lib/active_record/explain_subscriber.rb +2 -1
- data/lib/active_record/migration.rb +2 -2
- data/lib/active_record/persistence.rb +9 -8
- data/lib/active_record/railties/databases.rake +6 -6
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/store.rb +2 -0
- data/lib/active_record/timestamp.rb +1 -0
- data/lib/active_record/transactions.rb +22 -3
- data/lib/active_record/version.rb +2 -2
- metadata +9 -12
data/examples/performance.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
TIMES = (ENV['N'] || 10000).to_i
|
2
|
-
|
3
1
|
require File.expand_path('../../../load_paths', __FILE__)
|
4
2
|
require "active_record"
|
3
|
+
require 'benchmark/ips'
|
4
|
+
|
5
|
+
TIME = (ENV['BENCHMARK_TIME'] || 20).to_i
|
6
|
+
RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i
|
5
7
|
|
6
8
|
conn = { :adapter => 'sqlite3', :database => ':memory:' }
|
7
9
|
|
@@ -72,8 +74,8 @@ end
|
|
72
74
|
notes = ActiveRecord::Faker::LOREM.join ' '
|
73
75
|
today = Date.today
|
74
76
|
|
75
|
-
puts
|
76
|
-
|
77
|
+
puts "Inserting #{RECORDS} users and exhibits..."
|
78
|
+
RECORDS.times do
|
77
79
|
user = User.create(
|
78
80
|
:created_at => today,
|
79
81
|
:name => ActiveRecord::Faker.name,
|
@@ -88,9 +90,7 @@ puts 'Inserting 10,000 users and exhibits...'
|
|
88
90
|
)
|
89
91
|
end
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
-
Benchmark.bm(46) do |x|
|
93
|
+
Benchmark.ips(TIME) do |x|
|
94
94
|
ar_obj = Exhibit.find(1)
|
95
95
|
attrs = { :name => 'sam' }
|
96
96
|
attrs_first = { :name => 'sam' }
|
@@ -101,77 +101,72 @@ Benchmark.bm(46) do |x|
|
|
101
101
|
:created_at => Date.today
|
102
102
|
}
|
103
103
|
|
104
|
-
x.report("Model#id
|
105
|
-
|
104
|
+
x.report("Model#id") do
|
105
|
+
ar_obj.id
|
106
106
|
end
|
107
107
|
|
108
108
|
x.report 'Model.new (instantiation)' do
|
109
|
-
|
109
|
+
Exhibit.new
|
110
110
|
end
|
111
111
|
|
112
112
|
x.report 'Model.new (setting attributes)' do
|
113
|
-
|
113
|
+
Exhibit.new(attrs)
|
114
114
|
end
|
115
115
|
|
116
116
|
x.report 'Model.first' do
|
117
|
-
|
117
|
+
Exhibit.first.look
|
118
118
|
end
|
119
119
|
|
120
|
-
x.report
|
121
|
-
|
120
|
+
x.report("Model.all limit(100)") do
|
121
|
+
Exhibit.look Exhibit.limit(100)
|
122
122
|
end
|
123
123
|
|
124
|
-
x.report
|
125
|
-
|
124
|
+
x.report "Model.all limit(100) with relationship" do
|
125
|
+
Exhibit.feel Exhibit.limit(100).includes(:user)
|
126
126
|
end
|
127
127
|
|
128
|
-
x.report "Model.all limit(
|
129
|
-
|
128
|
+
x.report "Model.all limit(10,000)" do
|
129
|
+
Exhibit.look Exhibit.limit(10000)
|
130
130
|
end
|
131
131
|
|
132
|
-
x.report
|
133
|
-
|
132
|
+
x.report 'Model.named_scope' do
|
133
|
+
Exhibit.limit(10).with_name.with_notes
|
134
134
|
end
|
135
135
|
|
136
136
|
x.report 'Model.create' do
|
137
|
-
|
137
|
+
Exhibit.create(exhibit)
|
138
138
|
end
|
139
139
|
|
140
140
|
x.report 'Resource#attributes=' do
|
141
|
-
|
142
|
-
|
143
|
-
exhibit.attributes = attrs_second
|
144
|
-
}
|
141
|
+
e = Exhibit.new(attrs_first)
|
142
|
+
e.attributes = attrs_second
|
145
143
|
end
|
146
144
|
|
147
145
|
x.report 'Resource#update' do
|
148
|
-
|
146
|
+
Exhibit.first.update_attributes(:name => 'bob')
|
149
147
|
end
|
150
148
|
|
151
149
|
x.report 'Resource#destroy' do
|
152
|
-
|
150
|
+
Exhibit.first.destroy
|
153
151
|
end
|
154
152
|
|
155
153
|
x.report 'Model.transaction' do
|
156
|
-
|
154
|
+
Exhibit.transaction { Exhibit.new }
|
157
155
|
end
|
158
156
|
|
159
157
|
x.report 'Model.find(id)' do
|
160
|
-
|
161
|
-
TIMES.times { Exhibit.find(id) }
|
158
|
+
User.find(1)
|
162
159
|
end
|
163
160
|
|
164
161
|
x.report 'Model.find_by_sql' do
|
165
|
-
|
166
|
-
Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
|
167
|
-
}
|
162
|
+
Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
|
168
163
|
end
|
169
164
|
|
170
|
-
x.report "Model.log
|
171
|
-
|
165
|
+
x.report "Model.log" do
|
166
|
+
Exhibit.connection.send(:log, "hello", "world") {}
|
172
167
|
end
|
173
168
|
|
174
|
-
x.report "AR.execute(query)
|
175
|
-
|
169
|
+
x.report "AR.execute(query)" do
|
170
|
+
ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}")
|
176
171
|
end
|
177
172
|
end
|
@@ -105,6 +105,7 @@ module ActiveRecord
|
|
105
105
|
|
106
106
|
# See ActiveRecord::Associations::ClassMethods for documentation.
|
107
107
|
module Associations # :nodoc:
|
108
|
+
extend ActiveSupport::Autoload
|
108
109
|
extend ActiveSupport::Concern
|
109
110
|
|
110
111
|
# These classes will be loaded when associations are created.
|
@@ -134,11 +135,13 @@ module ActiveRecord
|
|
134
135
|
autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
|
135
136
|
end
|
136
137
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
138
|
+
eager_autoload do
|
139
|
+
autoload :Preloader, 'active_record/associations/preloader'
|
140
|
+
autoload :JoinDependency, 'active_record/associations/join_dependency'
|
141
|
+
autoload :AssociationScope, 'active_record/associations/association_scope'
|
142
|
+
autoload :AliasTracker, 'active_record/associations/alias_tracker'
|
143
|
+
autoload :JoinHelper, 'active_record/associations/join_helper'
|
144
|
+
end
|
142
145
|
|
143
146
|
# Clears out the association cache.
|
144
147
|
def clear_association_cache #:nodoc:
|
@@ -190,6 +190,8 @@ module ActiveRecord
|
|
190
190
|
# association, it will be used for the query. Otherwise, construct options and pass them with
|
191
191
|
# scope to the target class's +count+.
|
192
192
|
def count(column_name = nil, count_options = {})
|
193
|
+
return 0 if owner.new_record?
|
194
|
+
|
193
195
|
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
|
194
196
|
|
195
197
|
if options[:counter_sql] || options[:finder_sql]
|
@@ -362,7 +364,7 @@ module ActiveRecord
|
|
362
364
|
# replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
|
363
365
|
interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
|
364
366
|
count_with = $2.to_s
|
365
|
-
count_with = '*' if count_with.blank? || count_with =~ /,/
|
367
|
+
count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
|
366
368
|
"SELECT #{$1}COUNT(#{count_with}) FROM"
|
367
369
|
end
|
368
370
|
end
|
@@ -407,7 +409,7 @@ module ActiveRecord
|
|
407
409
|
if mem_index
|
408
410
|
mem_record = memory.delete_at(mem_index)
|
409
411
|
|
410
|
-
(record.attribute_names - mem_record.changes.keys).each do |name|
|
412
|
+
((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
|
411
413
|
mem_record[name] = record[name]
|
412
414
|
end
|
413
415
|
|
@@ -569,7 +571,7 @@ module ActiveRecord
|
|
569
571
|
args.shift if args.first.is_a?(Hash) && args.first.empty?
|
570
572
|
|
571
573
|
collection = fetch_first_or_last_using_find?(args) ? scoped : load_target
|
572
|
-
collection.send(type, *args)
|
574
|
+
collection.send(type, *args).tap {|it| set_inverse_instance it }
|
573
575
|
end
|
574
576
|
end
|
575
577
|
end
|
@@ -38,6 +38,20 @@ module ActiveRecord
|
|
38
38
|
super
|
39
39
|
end
|
40
40
|
|
41
|
+
def concat_records(records)
|
42
|
+
ensure_not_nested
|
43
|
+
|
44
|
+
records = super
|
45
|
+
|
46
|
+
if owner.new_record? && records
|
47
|
+
records.flatten.each do |record|
|
48
|
+
build_through_record(record)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
records
|
53
|
+
end
|
54
|
+
|
41
55
|
def insert_record(record, validate = true, raise = false)
|
42
56
|
ensure_not_nested
|
43
57
|
|
@@ -8,19 +8,21 @@ module ActiveRecord
|
|
8
8
|
raise_on_type_mismatch(record) if record
|
9
9
|
load_target
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
11
|
+
# If target and record are nil, or target is equal to record,
|
12
|
+
# we don't need to have transaction.
|
13
|
+
if (target || record) && target != record
|
14
|
+
reflection.klass.transaction do
|
15
|
+
remove_target!(options[:dependent]) if target && !target.destroyed?
|
16
|
+
|
17
|
+
if record
|
18
|
+
set_owner_attributes(record)
|
19
|
+
set_inverse_instance(record)
|
20
|
+
|
21
|
+
if owner.persisted? && save && !record.save
|
22
|
+
nullify_owner_attributes(record)
|
23
|
+
set_owner_attributes(target) if target
|
24
|
+
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
25
|
+
end
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -36,7 +38,7 @@ module ActiveRecord
|
|
36
38
|
when :destroy
|
37
39
|
target.destroy
|
38
40
|
when :nullify
|
39
|
-
target.
|
41
|
+
target.update_attribute(reflection.foreign_key, nil)
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
@@ -30,17 +30,21 @@ module ActiveRecord
|
|
30
30
|
# option references an association's column), it will fallback to the table
|
31
31
|
# join strategy.
|
32
32
|
class Preloader #:nodoc:
|
33
|
-
|
34
|
-
autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
|
35
|
-
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
|
36
|
-
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
|
33
|
+
extend ActiveSupport::Autoload
|
37
34
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
35
|
+
eager_autoload do
|
36
|
+
autoload :Association, 'active_record/associations/preloader/association'
|
37
|
+
autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
|
38
|
+
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
|
39
|
+
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
|
40
|
+
|
41
|
+
autoload :HasMany, 'active_record/associations/preloader/has_many'
|
42
|
+
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
|
43
|
+
autoload :HasOne, 'active_record/associations/preloader/has_one'
|
44
|
+
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
|
45
|
+
autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
|
46
|
+
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
|
47
|
+
end
|
44
48
|
|
45
49
|
attr_reader :records, :associations, :options, :model
|
46
50
|
|
@@ -55,12 +55,12 @@ module ActiveRecord
|
|
55
55
|
# The attribute already has an unsaved change.
|
56
56
|
if attribute_changed?(attr)
|
57
57
|
old = @changed_attributes[attr]
|
58
|
-
@changed_attributes.delete(attr) unless
|
58
|
+
@changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
|
59
59
|
else
|
60
60
|
old = clone_attribute_value(:read_attribute, attr)
|
61
61
|
# Save Time objects as TimeWithZone if time_zone_aware_attributes == true
|
62
62
|
old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
|
63
|
-
@changed_attributes[attr] = old if
|
63
|
+
@changed_attributes[attr] = old if _field_changed?(attr, old, value)
|
64
64
|
end
|
65
65
|
|
66
66
|
# Carry on.
|
@@ -77,7 +77,7 @@ module ActiveRecord
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
def
|
80
|
+
def _field_changed?(attr, old, value)
|
81
81
|
if column = column_for_attribute(attr)
|
82
82
|
if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
|
83
83
|
changes_from_zero_to_string?(old, value))
|
@@ -41,11 +41,14 @@ module ActiveRecord
|
|
41
41
|
unless time.acts_like?(:time)
|
42
42
|
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
|
43
43
|
end
|
44
|
-
time
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
zoned_time = time && time.in_time_zone rescue nil
|
45
|
+
rounded_time = round_usec(zoned_time)
|
46
|
+
rounded_value = round_usec(read_attribute("#{attr_name}"))
|
47
|
+
if (rounded_value != rounded_time) || (!rounded_value && original_time)
|
48
|
+
write_attribute("#{attr_name}", original_time)
|
49
|
+
#{attr_name}_will_change!
|
50
|
+
@attributes_cache["#{attr_name}"] = zoned_time
|
51
|
+
end
|
49
52
|
end
|
50
53
|
EOV
|
51
54
|
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
@@ -59,6 +62,12 @@ module ActiveRecord
|
|
59
62
|
time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
|
60
63
|
end
|
61
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def round_usec(value)
|
68
|
+
return unless value
|
69
|
+
value.change(:usec => 0)
|
70
|
+
end
|
62
71
|
end
|
63
72
|
end
|
64
73
|
end
|
data/lib/active_record/base.rb
CHANGED
@@ -450,12 +450,12 @@ module ActiveRecord #:nodoc:
|
|
450
450
|
private
|
451
451
|
|
452
452
|
def relation #:nodoc:
|
453
|
-
|
453
|
+
relation = Relation.new(self, arel_table)
|
454
454
|
|
455
455
|
if finder_needs_type_condition?
|
456
|
-
|
456
|
+
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
|
457
457
|
else
|
458
|
-
|
458
|
+
relation
|
459
459
|
end
|
460
460
|
end
|
461
461
|
end
|
@@ -489,7 +489,6 @@ module ActiveRecord #:nodoc:
|
|
489
489
|
@marked_for_destruction = false
|
490
490
|
@previously_changed = {}
|
491
491
|
@changed_attributes = {}
|
492
|
-
@relation = nil
|
493
492
|
|
494
493
|
ensure_proper_type
|
495
494
|
|
@@ -544,7 +543,7 @@ module ActiveRecord #:nodoc:
|
|
544
543
|
|
545
544
|
@changed_attributes = {}
|
546
545
|
self.class.column_defaults.each do |attr, orig_value|
|
547
|
-
@changed_attributes[attr] = orig_value if
|
546
|
+
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
|
548
547
|
end
|
549
548
|
|
550
549
|
@aggregation_cache = {}
|
@@ -54,8 +54,11 @@ module ActiveRecord
|
|
54
54
|
# your database connection configuration:
|
55
55
|
#
|
56
56
|
# * +pool+: number indicating size of connection pool (default 5)
|
57
|
-
# * +
|
58
|
-
# before giving up and raising a timeout error
|
57
|
+
# * +checkout _timeout+: number of seconds to block and wait for a
|
58
|
+
# connection before giving up and raising a timeout error
|
59
|
+
# (default 5 seconds). ('wait_timeout' supported for backwards
|
60
|
+
# compatibility, but conflicts with key used for different purpose
|
61
|
+
# by mysql2 adapter).
|
59
62
|
class ConnectionPool
|
60
63
|
include MonitorMixin
|
61
64
|
|
@@ -77,7 +80,10 @@ module ActiveRecord
|
|
77
80
|
@reserved_connections = {}
|
78
81
|
|
79
82
|
@queue = new_cond
|
80
|
-
|
83
|
+
# 'wait_timeout', the backward-compatible key, conflicts with spec key
|
84
|
+
# used by mysql2 for something entirely different, checkout_timeout
|
85
|
+
# preferred to avoid conflict and allow independent values.
|
86
|
+
@timeout = spec.config[:checkout_timeout] || spec.config[:wait_timeout] || 5
|
81
87
|
|
82
88
|
# default max pool size to 5
|
83
89
|
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
@@ -110,7 +116,7 @@ module ActiveRecord
|
|
110
116
|
# #release_connection releases the connection-thread association
|
111
117
|
# and returns the connection to the pool.
|
112
118
|
def release_connection(with_id = current_connection_id)
|
113
|
-
conn = @reserved_connections.delete(with_id)
|
119
|
+
conn = synchronize { @reserved_connections.delete(with_id) }
|
114
120
|
checkin conn if conn
|
115
121
|
end
|
116
122
|
|
@@ -68,6 +68,7 @@ module ActiveRecord
|
|
68
68
|
:database => config.path.sub(%r{^/},""),
|
69
69
|
:host => config.host }
|
70
70
|
spec.reject!{ |_,value| !value }
|
71
|
+
spec.map { |key,value| spec[key] = URI.unescape(value) if value.is_a?(String) }
|
71
72
|
if config.query
|
72
73
|
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
|
73
74
|
spec.merge!(options)
|
@@ -391,7 +391,7 @@ module ActiveRecord
|
|
391
391
|
# t.remove(:qualification)
|
392
392
|
# t.remove(:qualification, :experience)
|
393
393
|
def remove(*column_names)
|
394
|
-
@base.remove_column(@table_name, column_names)
|
394
|
+
@base.remove_column(@table_name, *column_names)
|
395
395
|
end
|
396
396
|
|
397
397
|
# Removes the given index from the table.
|