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.

Files changed (44) hide show
  1. data/CHANGELOG +25 -0
  2. data/lib/active_record/associations.rb +6 -5
  3. data/lib/active_record/associations/alias_tracker.rb +12 -24
  4. data/lib/active_record/associations/association.rb +56 -6
  5. data/lib/active_record/associations/belongs_to_association.rb +1 -1
  6. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +10 -12
  7. data/lib/active_record/associations/collection_association.rb +44 -31
  8. data/lib/active_record/associations/collection_proxy.rb +11 -4
  9. data/lib/active_record/associations/has_one_association.rb +1 -1
  10. data/lib/active_record/associations/join_helper.rb +1 -2
  11. data/lib/active_record/associations/preloader/association.rb +3 -2
  12. data/lib/active_record/associations/singular_association.rb +12 -4
  13. data/lib/active_record/attribute_methods.rb +1 -1
  14. data/lib/active_record/attribute_methods/primary_key.rb +1 -1
  15. data/lib/active_record/attribute_methods/read.rb +3 -2
  16. data/lib/active_record/autosave_association.rb +1 -1
  17. data/lib/active_record/base.rb +78 -30
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -6
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +4 -4
  21. data/lib/active_record/connection_adapters/column.rb +2 -0
  22. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -1
  23. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -1
  24. data/lib/active_record/connection_adapters/sqlite_adapter.rb +5 -0
  25. data/lib/active_record/locking/optimistic.rb +1 -1
  26. data/lib/active_record/migration.rb +6 -2
  27. data/lib/active_record/migration/command_recorder.rb +1 -1
  28. data/lib/active_record/named_scope.rb +19 -0
  29. data/lib/active_record/nested_attributes.rb +18 -12
  30. data/lib/active_record/persistence.rb +8 -3
  31. data/lib/active_record/query_cache.rb +8 -0
  32. data/lib/active_record/railtie.rb +23 -0
  33. data/lib/active_record/railties/databases.rake +18 -13
  34. data/lib/active_record/reflection.rb +26 -26
  35. data/lib/active_record/relation.rb +20 -12
  36. data/lib/active_record/relation/batches.rb +0 -2
  37. data/lib/active_record/relation/calculations.rb +11 -5
  38. data/lib/active_record/relation/finder_methods.rb +1 -1
  39. data/lib/active_record/relation/query_methods.rb +16 -7
  40. data/lib/active_record/relation/spawn_methods.rb +1 -1
  41. data/lib/active_record/session_store.rb +19 -9
  42. data/lib/active_record/test_case.rb +0 -1
  43. data/lib/active_record/version.rb +1 -1
  44. 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 = alias_tracker.pluralize(reflection.name, reflection.active_record)
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
- owner_keys = owners.map { |owner| owner[owner_key_name] }.compact.uniq
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
- owners_by_key[owner_key].each do |owner|
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
- build(attributes, options, &block).tap { |record| record.save }
21
+ create_record(attributes, options, &block)
22
22
  end
23
23
 
24
24
  def create!(attributes = {}, options = {}, &block)
25
- build(attributes, options, &block).tap { |record| record.save! }
25
+ create_record(attributes, options, true, &block)
26
26
  end
27
27
 
28
28
  def build(attributes = {}, options = {})
29
- record = reflection.build_association(attributes, options)
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
@@ -61,7 +61,7 @@ module ActiveRecord
61
61
  end
62
62
  end
63
63
 
64
- def respond_to?(*args)
64
+ def respond_to?(name, include_private = false)
65
65
  self.class.define_attribute_methods unless self.class.attribute_methods_generated?
66
66
  super
67
67
  end
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  # the record is not persisted? or has just been destroyed.
8
8
  def to_key
9
9
  key = send(self.class.primary_key)
10
- [key] if key
10
+ persisted? && key ? [key] : nil
11
11
  end
12
12
 
13
13
  module ClassMethods
@@ -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
- if respond_to? "_#{attr_name}"
103
- send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s)
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(:construct_scope) if association.respond_to?(:construct_scope)
350
+ association.send(:reset_scope) if association.respond_to?(:reset_scope)
351
351
  end
352
352
  end
353
353
 
@@ -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[:"#{self}_current_scope"]
1229
+ Thread.current["#{self}_current_scope"]
1213
1230
  end
1214
1231
 
1215
1232
  def current_scope=(scope) #:nodoc:
1216
- Thread.current[:"#{self}_current_scope"] = scope
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 apply_default_scope flag is used to prevent an infinite recursion situation where
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 unless apply_default_scope
1273
- self.apply_default_scope = false
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
- self.apply_default_scope = true
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
- result = yield self if block_given?
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
- "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
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, role)
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 elided after 50
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
- comparison_object.equal?(self) ||
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
- (@attributes.keys & self.class.serialized_attributes.keys).each do |key|
1900
- coder = self.class.serialized_attributes[key]
1901
- @attributes[key] = coder.load @attributes[key]
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
- unless self.class.descends_from_active_record?
1912
- write_attribute(self.class.inheritance_column, self.class.sti_name)
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.scoped.scope_for_create.each do |att,value|
2087
- respond_to?("#{att}=") && send("#{att}=", value)
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 = nil)
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 suppliers_name_index in the suppliers table
390
- # t.remove_index :name
391
- # ====== Remove the index named accounts_branch_id_index in the accounts table
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 accounts_branch_id_party_id_index in the accounts table
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 accounts table
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)