activerecord 3.1.0.rc4 → 3.1.0.rc5
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 +25 -0
- data/lib/active_record/associations.rb +6 -5
- data/lib/active_record/associations/alias_tracker.rb +12 -24
- data/lib/active_record/associations/association.rb +56 -6
- data/lib/active_record/associations/belongs_to_association.rb +1 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +10 -12
- data/lib/active_record/associations/collection_association.rb +44 -31
- data/lib/active_record/associations/collection_proxy.rb +11 -4
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -2
- data/lib/active_record/associations/preloader/association.rb +3 -2
- data/lib/active_record/associations/singular_association.rb +12 -4
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +3 -2
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +78 -30
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +4 -4
- data/lib/active_record/connection_adapters/column.rb +2 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +5 -0
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/migration.rb +6 -2
- data/lib/active_record/migration/command_recorder.rb +1 -1
- data/lib/active_record/named_scope.rb +19 -0
- data/lib/active_record/nested_attributes.rb +18 -12
- data/lib/active_record/persistence.rb +8 -3
- data/lib/active_record/query_cache.rb +8 -0
- data/lib/active_record/railtie.rb +23 -0
- data/lib/active_record/railties/databases.rake +18 -13
- data/lib/active_record/reflection.rb +26 -26
- data/lib/active_record/relation.rb +20 -12
- data/lib/active_record/relation/batches.rb +0 -2
- data/lib/active_record/relation/calculations.rb +11 -5
- data/lib/active_record/relation/finder_methods.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +16 -7
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/session_store.rb +19 -9
- data/lib/active_record/test_case.rb +0 -1
- data/lib/active_record/version.rb +1 -1
- metadata +15 -29
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
19
19
|
|
20
20
|
if owner.persisted? && save && !record.save
|
21
21
|
nullify_owner_attributes(record)
|
22
|
-
set_owner_attributes(target)
|
22
|
+
set_owner_attributes(target) if target
|
23
23
|
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
24
24
|
end
|
25
25
|
end
|
@@ -32,8 +32,7 @@ module ActiveRecord
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def table_alias_for(reflection, join = false)
|
35
|
-
name =
|
36
|
-
name << "_#{alias_suffix}"
|
35
|
+
name = "#{reflection.plural_name}_#{alias_suffix}"
|
37
36
|
name << "_join" if join
|
38
37
|
name
|
39
38
|
end
|
@@ -68,7 +68,8 @@ module ActiveRecord
|
|
68
68
|
private
|
69
69
|
|
70
70
|
def associated_records_by_owner
|
71
|
-
|
71
|
+
owners_map = owners_by_key
|
72
|
+
owner_keys = owners_map.keys.compact
|
72
73
|
|
73
74
|
if klass.nil? || owner_keys.empty?
|
74
75
|
records = []
|
@@ -84,7 +85,7 @@ module ActiveRecord
|
|
84
85
|
records.each do |record|
|
85
86
|
owner_key = record[association_key_name].to_s
|
86
87
|
|
87
|
-
|
88
|
+
owners_map[owner_key].each do |owner|
|
88
89
|
records_by_owner[owner] << record
|
89
90
|
end
|
90
91
|
end
|
@@ -18,16 +18,15 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def create(attributes = {}, options = {}, &block)
|
21
|
-
|
21
|
+
create_record(attributes, options, &block)
|
22
22
|
end
|
23
23
|
|
24
24
|
def create!(attributes = {}, options = {}, &block)
|
25
|
-
|
25
|
+
create_record(attributes, options, true, &block)
|
26
26
|
end
|
27
27
|
|
28
28
|
def build(attributes = {}, options = {})
|
29
|
-
record =
|
30
|
-
record.assign_attributes(create_scope.except(*record.changed), :without_protection => true)
|
29
|
+
record = build_record(attributes, options)
|
31
30
|
yield(record) if block_given?
|
32
31
|
set_new_record(record)
|
33
32
|
record
|
@@ -51,6 +50,15 @@ module ActiveRecord
|
|
51
50
|
def set_new_record(record)
|
52
51
|
replace(record)
|
53
52
|
end
|
53
|
+
|
54
|
+
def create_record(attributes, options, raise_error = false)
|
55
|
+
record = build_record(attributes, options)
|
56
|
+
yield(record) if block_given?
|
57
|
+
saved = record.save
|
58
|
+
set_new_record(record)
|
59
|
+
raise RecordInvalid.new(record) if !saved && raise_error
|
60
|
+
record
|
61
|
+
end
|
54
62
|
end
|
55
63
|
end
|
56
64
|
end
|
@@ -99,8 +99,9 @@ module ActiveRecord
|
|
99
99
|
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
100
100
|
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
101
101
|
def read_attribute(attr_name)
|
102
|
-
|
103
|
-
|
102
|
+
method = "_#{attr_name}"
|
103
|
+
if respond_to? method
|
104
|
+
send method if @attributes.has_key?(attr_name.to_s)
|
104
105
|
else
|
105
106
|
_read_attribute attr_name
|
106
107
|
end
|
@@ -347,7 +347,7 @@ module ActiveRecord
|
|
347
347
|
end
|
348
348
|
|
349
349
|
# reconstruct the scope now that we know the owner's id
|
350
|
-
association.send(:
|
350
|
+
association.send(:reset_scope) if association.respond_to?(:reset_scope)
|
351
351
|
end
|
352
352
|
end
|
353
353
|
|
data/lib/active_record/base.rb
CHANGED
@@ -23,6 +23,7 @@ require 'active_support/core_ext/module/delegation'
|
|
23
23
|
require 'active_support/core_ext/module/introspection'
|
24
24
|
require 'active_support/core_ext/object/duplicable'
|
25
25
|
require 'active_support/core_ext/object/blank'
|
26
|
+
require 'active_support/deprecation'
|
26
27
|
require 'arel'
|
27
28
|
require 'active_record/errors'
|
28
29
|
require 'active_record/log_subscriber'
|
@@ -428,10 +429,6 @@ module ActiveRecord #:nodoc:
|
|
428
429
|
class_attribute :default_scopes, :instance_writer => false
|
429
430
|
self.default_scopes = []
|
430
431
|
|
431
|
-
# Boolean flag to prevent infinite recursion when evaluating default scopes
|
432
|
-
class_attribute :apply_default_scope, :instance_writer => false
|
433
|
-
self.apply_default_scope = true
|
434
|
-
|
435
432
|
# Returns a hash of all the attributes that have been specified for serialization as
|
436
433
|
# keys and their class restriction as values.
|
437
434
|
class_attribute :serialized_attributes
|
@@ -714,6 +711,12 @@ module ActiveRecord #:nodoc:
|
|
714
711
|
connection_pool.columns_hash[table_name]
|
715
712
|
end
|
716
713
|
|
714
|
+
# Returns a hash where the keys are column names and the values are
|
715
|
+
# default values when instantiating the AR object for this table.
|
716
|
+
def column_defaults
|
717
|
+
connection_pool.column_defaults[table_name]
|
718
|
+
end
|
719
|
+
|
717
720
|
# Returns an array of column names as strings.
|
718
721
|
def column_names
|
719
722
|
@column_names ||= columns.map { |column| column.name }
|
@@ -1054,6 +1057,13 @@ module ActiveRecord #:nodoc:
|
|
1054
1057
|
if match = DynamicFinderMatch.match(method_id)
|
1055
1058
|
attribute_names = match.attribute_names
|
1056
1059
|
super unless all_attributes_exists?(attribute_names)
|
1060
|
+
if arguments.size < attribute_names.size
|
1061
|
+
ActiveSupport::Deprecation.warn(
|
1062
|
+
"Calling dynamic finder with less number of arguments than the number of attributes in " \
|
1063
|
+
"method name is deprecated and will raise an ArguementError in the next version of Rails. " \
|
1064
|
+
"Please passing `nil' to the argument you want it to be nil."
|
1065
|
+
)
|
1066
|
+
end
|
1057
1067
|
if match.finder?
|
1058
1068
|
options = arguments.extract_options!
|
1059
1069
|
relation = options.any? ? scoped(options) : scoped
|
@@ -1064,6 +1074,13 @@ module ActiveRecord #:nodoc:
|
|
1064
1074
|
elsif match = DynamicScopeMatch.match(method_id)
|
1065
1075
|
attribute_names = match.attribute_names
|
1066
1076
|
super unless all_attributes_exists?(attribute_names)
|
1077
|
+
if arguments.size < attribute_names.size
|
1078
|
+
ActiveSupport::Deprecation.warn(
|
1079
|
+
"Calling dynamic scope with less number of arguments than the number of attributes in " \
|
1080
|
+
"method name is deprecated and will raise an ArguementError in the next version of Rails. " \
|
1081
|
+
"Please passing `nil' to the argument you want it to be nil."
|
1082
|
+
)
|
1083
|
+
end
|
1067
1084
|
if match.scope?
|
1068
1085
|
self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
1069
1086
|
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
@@ -1209,11 +1226,11 @@ MSG
|
|
1209
1226
|
end
|
1210
1227
|
|
1211
1228
|
def current_scope #:nodoc:
|
1212
|
-
Thread.current[
|
1229
|
+
Thread.current["#{self}_current_scope"]
|
1213
1230
|
end
|
1214
1231
|
|
1215
1232
|
def current_scope=(scope) #:nodoc:
|
1216
|
-
Thread.current[
|
1233
|
+
Thread.current["#{self}_current_scope"] = scope
|
1217
1234
|
end
|
1218
1235
|
|
1219
1236
|
# Use this macro in your model to set a default scope for all operations on
|
@@ -1266,11 +1283,11 @@ MSG
|
|
1266
1283
|
self.default_scopes = default_scopes + [scope]
|
1267
1284
|
end
|
1268
1285
|
|
1269
|
-
# The
|
1286
|
+
# The @ignore_default_scope flag is used to prevent an infinite recursion situation where
|
1270
1287
|
# a default scope references a scope which has a default scope which references a scope...
|
1271
1288
|
def build_default_scope #:nodoc:
|
1272
|
-
return
|
1273
|
-
|
1289
|
+
return if defined?(@ignore_default_scope) && @ignore_default_scope
|
1290
|
+
@ignore_default_scope = true
|
1274
1291
|
|
1275
1292
|
if method(:default_scope).owner != Base.singleton_class
|
1276
1293
|
default_scope
|
@@ -1286,7 +1303,7 @@ MSG
|
|
1286
1303
|
end
|
1287
1304
|
end
|
1288
1305
|
ensure
|
1289
|
-
|
1306
|
+
@ignore_default_scope = false
|
1290
1307
|
end
|
1291
1308
|
|
1292
1309
|
# Returns the class type of the record using the current module as a prefix. So descendants of
|
@@ -1532,6 +1549,7 @@ MSG
|
|
1532
1549
|
@marked_for_destruction = false
|
1533
1550
|
@previously_changed = {}
|
1534
1551
|
@changed_attributes = {}
|
1552
|
+
@relation = nil
|
1535
1553
|
|
1536
1554
|
ensure_proper_type
|
1537
1555
|
set_serialized_attributes
|
@@ -1540,9 +1558,8 @@ MSG
|
|
1540
1558
|
|
1541
1559
|
assign_attributes(attributes, options) if attributes
|
1542
1560
|
|
1543
|
-
|
1561
|
+
yield self if block_given?
|
1544
1562
|
run_callbacks :initialize
|
1545
|
-
result
|
1546
1563
|
end
|
1547
1564
|
|
1548
1565
|
# Populate +coder+ with attributes about this record that should be
|
@@ -1573,6 +1590,7 @@ MSG
|
|
1573
1590
|
# post.title # => 'hello world'
|
1574
1591
|
def init_with(coder)
|
1575
1592
|
@attributes = coder['attributes']
|
1593
|
+
@relation = nil
|
1576
1594
|
|
1577
1595
|
set_serialized_attributes
|
1578
1596
|
|
@@ -1636,7 +1654,8 @@ MSG
|
|
1636
1654
|
when new_record?
|
1637
1655
|
"#{self.class.model_name.cache_key}/new"
|
1638
1656
|
when timestamp = self[:updated_at]
|
1639
|
-
|
1657
|
+
timestamp = timestamp.utc.to_s(:number)
|
1658
|
+
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
|
1640
1659
|
else
|
1641
1660
|
"#{self.class.model_name.cache_key}/#{id}"
|
1642
1661
|
end
|
@@ -1720,12 +1739,11 @@ MSG
|
|
1720
1739
|
return unless new_attributes
|
1721
1740
|
|
1722
1741
|
attributes = new_attributes.stringify_keys
|
1723
|
-
role = options[:as] || :default
|
1724
|
-
|
1725
1742
|
multi_parameter_attributes = []
|
1743
|
+
@mass_assignment_options = options
|
1726
1744
|
|
1727
1745
|
unless options[:without_protection]
|
1728
|
-
attributes = sanitize_for_mass_assignment(attributes,
|
1746
|
+
attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
|
1729
1747
|
end
|
1730
1748
|
|
1731
1749
|
attributes.each do |k, v|
|
@@ -1738,6 +1756,7 @@ MSG
|
|
1738
1756
|
end
|
1739
1757
|
end
|
1740
1758
|
|
1759
|
+
@mass_assignment_options = nil
|
1741
1760
|
assign_multiparameter_attributes(multi_parameter_attributes)
|
1742
1761
|
end
|
1743
1762
|
|
@@ -1747,7 +1766,7 @@ MSG
|
|
1747
1766
|
end
|
1748
1767
|
|
1749
1768
|
# Returns an <tt>#inspect</tt>-like string for the value of the
|
1750
|
-
# attribute +attr_name+. String attributes are
|
1769
|
+
# attribute +attr_name+. String attributes are truncated upto 50
|
1751
1770
|
# characters, and Date and Time attributes are returned in the
|
1752
1771
|
# <tt>:db</tt> format. Other attributes return the value of
|
1753
1772
|
# <tt>#inspect</tt> without modification.
|
@@ -1792,16 +1811,12 @@ MSG
|
|
1792
1811
|
# Note also that destroying a record preserves its ID in the model instance, so deleted
|
1793
1812
|
# models are still comparable.
|
1794
1813
|
def ==(comparison_object)
|
1795
|
-
|
1814
|
+
super ||
|
1796
1815
|
comparison_object.instance_of?(self.class) &&
|
1797
1816
|
id.present? &&
|
1798
1817
|
comparison_object.id == id
|
1799
1818
|
end
|
1800
|
-
|
1801
|
-
# Delegates to ==
|
1802
|
-
def eql?(comparison_object)
|
1803
|
-
self == comparison_object
|
1804
|
-
end
|
1819
|
+
alias :eql? :==
|
1805
1820
|
|
1806
1821
|
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
1807
1822
|
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
@@ -1819,6 +1834,15 @@ MSG
|
|
1819
1834
|
@attributes.frozen?
|
1820
1835
|
end
|
1821
1836
|
|
1837
|
+
# Allows sort on objects
|
1838
|
+
def <=>(other_object)
|
1839
|
+
if other_object.is_a?(self.class)
|
1840
|
+
self.to_key <=> other_object.to_key
|
1841
|
+
else
|
1842
|
+
nil
|
1843
|
+
end
|
1844
|
+
end
|
1845
|
+
|
1822
1846
|
# Backport dup from 1.9 so that initialize_dup() gets called
|
1823
1847
|
unless Object.respond_to?(:initialize_dup)
|
1824
1848
|
def dup # :nodoc:
|
@@ -1893,12 +1917,33 @@ MSG
|
|
1893
1917
|
value
|
1894
1918
|
end
|
1895
1919
|
|
1920
|
+
def mass_assignment_options
|
1921
|
+
@mass_assignment_options ||= {}
|
1922
|
+
end
|
1923
|
+
|
1924
|
+
def mass_assignment_role
|
1925
|
+
mass_assignment_options[:as] || :default
|
1926
|
+
end
|
1927
|
+
|
1896
1928
|
private
|
1897
1929
|
|
1930
|
+
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
|
1931
|
+
# of the array, and then rescues from the possible NoMethodError. If those elements are
|
1932
|
+
# ActiveRecord::Base's, then this triggers the various method_missing's that we have,
|
1933
|
+
# which significantly impacts upon performance.
|
1934
|
+
#
|
1935
|
+
# So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
|
1936
|
+
#
|
1937
|
+
# See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
|
1938
|
+
def to_ary # :nodoc:
|
1939
|
+
nil
|
1940
|
+
end
|
1941
|
+
|
1898
1942
|
def set_serialized_attributes
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1943
|
+
sattrs = self.class.serialized_attributes
|
1944
|
+
|
1945
|
+
sattrs.each do |key, coder|
|
1946
|
+
@attributes[key] = coder.load @attributes[key] if @attributes.key?(key)
|
1902
1947
|
end
|
1903
1948
|
end
|
1904
1949
|
|
@@ -1908,8 +1953,9 @@ MSG
|
|
1908
1953
|
# do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
|
1909
1954
|
# No such attribute would be set for objects of the Message class in that example.
|
1910
1955
|
def ensure_proper_type
|
1911
|
-
|
1912
|
-
|
1956
|
+
klass = self.class
|
1957
|
+
if klass.finder_needs_type_condition?
|
1958
|
+
write_attribute(klass.inheritance_column, klass.sti_name)
|
1913
1959
|
end
|
1914
1960
|
end
|
1915
1961
|
|
@@ -2083,8 +2129,10 @@ MSG
|
|
2083
2129
|
end
|
2084
2130
|
|
2085
2131
|
def populate_with_current_scope_attributes
|
2086
|
-
self.class.
|
2087
|
-
|
2132
|
+
return unless self.class.scope_attributes?
|
2133
|
+
|
2134
|
+
self.class.scope_attributes.each do |att,value|
|
2135
|
+
send("#{att}=", value) if respond_to?("#{att}=")
|
2088
2136
|
end
|
2089
2137
|
end
|
2090
2138
|
|
@@ -60,6 +60,7 @@ module ActiveRecord
|
|
60
60
|
attr_accessor :automatic_reconnect
|
61
61
|
attr_reader :spec, :connections
|
62
62
|
attr_reader :columns, :columns_hash, :primary_keys, :tables
|
63
|
+
attr_reader :column_defaults
|
63
64
|
|
64
65
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
65
66
|
# object which describes database connection information (e.g. adapter,
|
@@ -106,6 +107,12 @@ module ActiveRecord
|
|
106
107
|
}]
|
107
108
|
end
|
108
109
|
|
110
|
+
@column_defaults = Hash.new do |h, table_name|
|
111
|
+
h[table_name] = Hash[columns[table_name].map { |col|
|
112
|
+
[col.name, col.default]
|
113
|
+
}]
|
114
|
+
end
|
115
|
+
|
109
116
|
@primary_keys = Hash.new do |h, table_name|
|
110
117
|
h[table_name] = with_connection do |conn|
|
111
118
|
table_exists?(table_name) ? conn.primary_key(table_name) : 'id'
|
@@ -119,6 +126,7 @@ module ActiveRecord
|
|
119
126
|
|
120
127
|
with_connection do |conn|
|
121
128
|
conn.tables.each { |table| @tables[table] = true }
|
129
|
+
@tables[name] = true if !@tables.key?(name) && conn.table_exists?(name)
|
122
130
|
end
|
123
131
|
|
124
132
|
@tables.key? name
|
@@ -132,6 +140,7 @@ module ActiveRecord
|
|
132
140
|
def clear_cache!
|
133
141
|
@columns.clear
|
134
142
|
@columns_hash.clear
|
143
|
+
@column_defaults.clear
|
135
144
|
@tables.clear
|
136
145
|
end
|
137
146
|
|
@@ -139,6 +148,7 @@ module ActiveRecord
|
|
139
148
|
def clear_table_cache!(table_name)
|
140
149
|
@columns.delete table_name
|
141
150
|
@columns_hash.delete table_name
|
151
|
+
@column_defaults.delete table_name
|
142
152
|
@primary_keys.delete table_name
|
143
153
|
end
|
144
154
|
|
@@ -425,6 +435,14 @@ module ActiveRecord
|
|
425
435
|
@testing = testing
|
426
436
|
end
|
427
437
|
|
438
|
+
def method_missing(method_sym, *arguments, &block)
|
439
|
+
@body.send(method_sym, *arguments, &block)
|
440
|
+
end
|
441
|
+
|
442
|
+
def respond_to?(method_sym, include_private = false)
|
443
|
+
super || @body.respond_to?(method_sym)
|
444
|
+
end
|
445
|
+
|
428
446
|
def each(&block)
|
429
447
|
body.each(&block)
|
430
448
|
end
|
@@ -328,7 +328,7 @@ module ActiveRecord
|
|
328
328
|
end
|
329
329
|
|
330
330
|
# Checks to see if a column exists. See SchemaStatements#column_exists?
|
331
|
-
def column_exists?(column_name, type = nil, options =
|
331
|
+
def column_exists?(column_name, type = nil, options = {})
|
332
332
|
@base.column_exists?(@table_name, column_name, type, options)
|
333
333
|
end
|
334
334
|
|
@@ -386,13 +386,13 @@ module ActiveRecord
|
|
386
386
|
# Removes the given index from the table.
|
387
387
|
#
|
388
388
|
# ===== Examples
|
389
|
-
# ====== Remove the
|
390
|
-
# t.remove_index :
|
391
|
-
# ====== Remove the index named
|
389
|
+
# ====== Remove the index_table_name_on_column in the table_name table
|
390
|
+
# t.remove_index :column
|
391
|
+
# ====== Remove the index named index_table_name_on_branch_id in the table_name table
|
392
392
|
# t.remove_index :column => :branch_id
|
393
|
-
# ====== Remove the index named
|
393
|
+
# ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
|
394
394
|
# t.remove_index :column => [:branch_id, :party_id]
|
395
|
-
# ====== Remove the index named by_branch_party in the
|
395
|
+
# ====== Remove the index named by_branch_party in the table_name table
|
396
396
|
# t.remove_index :name => :by_branch_party
|
397
397
|
def remove_index(options = {})
|
398
398
|
@base.remove_index(@table_name, options)
|