activerecord 4.0.4 → 4.1.16

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1632 -1797
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/examples/performance.rb +30 -18
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +4 -0
  9. data/lib/active_record/associations/alias_tracker.rb +49 -29
  10. data/lib/active_record/associations/association.rb +9 -17
  11. data/lib/active_record/associations/association_scope.rb +59 -49
  12. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
  14. data/lib/active_record/associations/builder/association.rb +84 -54
  15. data/lib/active_record/associations/builder/belongs_to.rb +90 -58
  16. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
  18. data/lib/active_record/associations/builder/has_many.rb +3 -3
  19. data/lib/active_record/associations/builder/has_one.rb +5 -7
  20. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  21. data/lib/active_record/associations/collection_association.rb +121 -111
  22. data/lib/active_record/associations/collection_proxy.rb +73 -18
  23. data/lib/active_record/associations/has_many_association.rb +14 -11
  24. data/lib/active_record/associations/has_many_through_association.rb +33 -6
  25. data/lib/active_record/associations/has_one_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
  27. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  28. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  29. data/lib/active_record/associations/join_dependency.rb +208 -168
  30. data/lib/active_record/associations/preloader/association.rb +69 -27
  31. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  35. data/lib/active_record/associations/preloader.rb +63 -49
  36. data/lib/active_record/associations/singular_association.rb +6 -5
  37. data/lib/active_record/associations/through_association.rb +30 -9
  38. data/lib/active_record/associations.rb +116 -42
  39. data/lib/active_record/attribute_assignment.rb +6 -3
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  41. data/lib/active_record/attribute_methods/dirty.rb +35 -26
  42. data/lib/active_record/attribute_methods/primary_key.rb +8 -1
  43. data/lib/active_record/attribute_methods/read.rb +56 -29
  44. data/lib/active_record/attribute_methods/serialization.rb +44 -12
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
  46. data/lib/active_record/attribute_methods/write.rb +59 -26
  47. data/lib/active_record/attribute_methods.rb +82 -43
  48. data/lib/active_record/autosave_association.rb +209 -194
  49. data/lib/active_record/base.rb +6 -2
  50. data/lib/active_record/callbacks.rb +2 -2
  51. data/lib/active_record/coders/json.rb +13 -0
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
  76. data/lib/active_record/connection_handling.rb +39 -5
  77. data/lib/active_record/core.rb +38 -54
  78. data/lib/active_record/counter_cache.rb +9 -10
  79. data/lib/active_record/dynamic_matchers.rb +6 -2
  80. data/lib/active_record/enum.rb +199 -0
  81. data/lib/active_record/errors.rb +22 -5
  82. data/lib/active_record/fixture_set/file.rb +2 -1
  83. data/lib/active_record/fixtures.rb +173 -76
  84. data/lib/active_record/gem_version.rb +15 -0
  85. data/lib/active_record/inheritance.rb +23 -9
  86. data/lib/active_record/integration.rb +54 -1
  87. data/lib/active_record/locking/optimistic.rb +7 -2
  88. data/lib/active_record/locking/pessimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +6 -13
  90. data/lib/active_record/migration/command_recorder.rb +8 -2
  91. data/lib/active_record/migration.rb +91 -56
  92. data/lib/active_record/model_schema.rb +7 -14
  93. data/lib/active_record/nested_attributes.rb +25 -13
  94. data/lib/active_record/no_touching.rb +52 -0
  95. data/lib/active_record/null_relation.rb +26 -6
  96. data/lib/active_record/persistence.rb +23 -29
  97. data/lib/active_record/querying.rb +15 -12
  98. data/lib/active_record/railtie.rb +12 -61
  99. data/lib/active_record/railties/databases.rake +37 -56
  100. data/lib/active_record/readonly_attributes.rb +0 -6
  101. data/lib/active_record/reflection.rb +230 -79
  102. data/lib/active_record/relation/batches.rb +74 -24
  103. data/lib/active_record/relation/calculations.rb +52 -48
  104. data/lib/active_record/relation/delegation.rb +54 -39
  105. data/lib/active_record/relation/finder_methods.rb +210 -67
  106. data/lib/active_record/relation/merger.rb +15 -12
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
  109. data/lib/active_record/relation/predicate_builder.rb +81 -40
  110. data/lib/active_record/relation/query_methods.rb +185 -108
  111. data/lib/active_record/relation/spawn_methods.rb +8 -5
  112. data/lib/active_record/relation.rb +79 -84
  113. data/lib/active_record/result.rb +45 -6
  114. data/lib/active_record/runtime_registry.rb +5 -0
  115. data/lib/active_record/sanitization.rb +4 -4
  116. data/lib/active_record/schema_dumper.rb +18 -6
  117. data/lib/active_record/schema_migration.rb +31 -18
  118. data/lib/active_record/scoping/default.rb +5 -18
  119. data/lib/active_record/scoping/named.rb +14 -29
  120. data/lib/active_record/scoping.rb +5 -0
  121. data/lib/active_record/store.rb +67 -18
  122. data/lib/active_record/tasks/database_tasks.rb +66 -26
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
  124. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  125. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  126. data/lib/active_record/timestamp.rb +6 -6
  127. data/lib/active_record/transactions.rb +10 -12
  128. data/lib/active_record/validations/presence.rb +1 -1
  129. data/lib/active_record/validations/uniqueness.rb +19 -9
  130. data/lib/active_record/version.rb +4 -7
  131. data/lib/active_record.rb +5 -7
  132. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  133. data/lib/rails/generators/active_record/migration.rb +18 -0
  134. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  135. data/lib/rails/generators/active_record.rb +2 -8
  136. metadata +18 -30
  137. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  138. data/lib/active_record/associations/join_helper.rb +0 -45
  139. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  141. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  142. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  143. data/lib/active_record/test_case.rb +0 -96
@@ -4,6 +4,12 @@ require 'active_support/core_ext/module/remove_method'
4
4
  require 'active_record/errors'
5
5
 
6
6
  module ActiveRecord
7
+ class AssociationNotFoundError < ConfigurationError #:nodoc:
8
+ def initialize(record, association_name)
9
+ super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
10
+ end
11
+ end
12
+
7
13
  class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
8
14
  def initialize(reflection, associated_class = nil)
9
15
  super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
@@ -44,7 +50,7 @@ module ActiveRecord
44
50
  def initialize(reflection)
45
51
  through_reflection = reflection.through_reflection
46
52
  source_reflection_names = reflection.source_reflection_names
47
- source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
53
+ source_associations = reflection.through_reflection.klass._reflections.keys
48
54
  super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
49
55
  end
50
56
  end
@@ -73,21 +79,15 @@ module ActiveRecord
73
79
  end
74
80
  end
75
81
 
76
- class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
77
- def initialize(reflection)
78
- super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
79
- end
80
- end
81
-
82
82
  class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
83
83
  def initialize(reflection)
84
- super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
84
+ super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
85
85
  end
86
86
  end
87
87
 
88
88
  class ReadOnlyAssociation < ActiveRecordError #:nodoc:
89
89
  def initialize(reflection)
90
- super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
90
+ super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
91
91
  end
92
92
  end
93
93
 
@@ -114,7 +114,6 @@ module ActiveRecord
114
114
 
115
115
  autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
116
116
  autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
117
- autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
118
117
  autoload :HasManyAssociation, 'active_record/associations/has_many_association'
119
118
  autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
120
119
  autoload :HasOneAssociation, 'active_record/associations/has_one_association'
@@ -137,7 +136,6 @@ module ActiveRecord
137
136
  autoload :JoinDependency, 'active_record/associations/join_dependency'
138
137
  autoload :AssociationScope, 'active_record/associations/association_scope'
139
138
  autoload :AliasTracker, 'active_record/associations/alias_tracker'
140
- autoload :JoinHelper, 'active_record/associations/join_helper'
141
139
  end
142
140
 
143
141
  # Clears out the association cache.
@@ -153,7 +151,7 @@ module ActiveRecord
153
151
  association = association_instance_get(name)
154
152
 
155
153
  if association.nil?
156
- reflection = self.class.reflect_on_association(name)
154
+ raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
157
155
  association = reflection.association_class.new(self, reflection)
158
156
  association_instance_set(name, association)
159
157
  end
@@ -164,7 +162,7 @@ module ActiveRecord
164
162
  private
165
163
  # Returns the specified association instance if it responds to :loaded?, nil otherwise.
166
164
  def association_instance_get(name)
167
- @association_cache[name.to_sym]
165
+ @association_cache[name]
168
166
  end
169
167
 
170
168
  # Set the specified association instance.
@@ -172,7 +170,7 @@ module ActiveRecord
172
170
  @association_cache[name] = association
173
171
  end
174
172
 
175
- # Associations are a set of macro-like class methods for tying objects together through
173
+ # \Associations are a set of macro-like class methods for tying objects together through
176
174
  # foreign keys. They express relationships like "Project has one Project Manager"
177
175
  # or "Project belongs to a Portfolio". Each macro adds a number of methods to the
178
176
  # class which are specialized according to the collection or association symbol and the
@@ -365,11 +363,11 @@ module ActiveRecord
365
363
  # there is some special behavior you should be aware of, mostly involving the saving of
366
364
  # associated objects.
367
365
  #
368
- # You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
366
+ # You can set the <tt>:autosave</tt> option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
369
367
  # <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
370
368
  # to +true+ will _always_ save the members, whereas setting it to +false+ will
371
- # _never_ save the members. More details about :autosave option is available at
372
- # autosave_association.rb .
369
+ # _never_ save the members. More details about <tt>:autosave</tt> option is available at
370
+ # AutosaveAssociation.
373
371
  #
374
372
  # === One-to-one associations
375
373
  #
@@ -402,7 +400,7 @@ module ActiveRecord
402
400
  #
403
401
  # == Customizing the query
404
402
  #
405
- # Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
403
+ # \Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
406
404
  # to customize them. For example, to add a condition:
407
405
  #
408
406
  # class Blog < ActiveRecord::Base
@@ -568,6 +566,8 @@ module ActiveRecord
568
566
  # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
569
567
  # @group.avatars.delete(@group.avatars.last) # so would this
570
568
  #
569
+ # == Setting Inverses
570
+ #
571
571
  # If you are using a +belongs_to+ on the join model, it is a good idea to set the
572
572
  # <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example
573
573
  # works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
@@ -584,7 +584,27 @@ module ActiveRecord
584
584
  # belongs_to :tag, inverse_of: :taggings
585
585
  # end
586
586
  #
587
- # == Nested Associations
587
+ # If you do not set the <tt>:inverse_of</tt> record, the association will
588
+ # do its best to match itself up with the correct inverse. Automatic
589
+ # inverse detection only works on <tt>has_many</tt>, <tt>has_one</tt>, and
590
+ # <tt>belongs_to</tt> associations.
591
+ #
592
+ # Extra options on the associations, as defined in the
593
+ # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
594
+ # also prevent the association's inverse from being found automatically.
595
+ #
596
+ # The automatic guessing of the inverse association uses a heuristic based
597
+ # on the name of the class, so it may not work for all associations,
598
+ # especially the ones with non-standard names.
599
+ #
600
+ # You can turn off the automatic detection of inverse associations by setting
601
+ # the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
602
+ #
603
+ # class Taggable < ActiveRecord::Base
604
+ # belongs_to :tag, inverse_of: false
605
+ # end
606
+ #
607
+ # == Nested \Associations
588
608
  #
589
609
  # You can actually specify *any* association with the <tt>:through</tt> option, including an
590
610
  # association which has a <tt>:through</tt> option itself. For example:
@@ -627,7 +647,7 @@ module ActiveRecord
627
647
  # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
628
648
  # intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
629
649
  #
630
- # == Polymorphic Associations
650
+ # == Polymorphic \Associations
631
651
  #
632
652
  # Polymorphic associations on models are not restricted on what types of models they
633
653
  # can be associated with. Rather, they specify an interface that a +has_many+ association
@@ -654,11 +674,14 @@ module ActiveRecord
654
674
  # and member posts that use the posts table for STI. In this case, there must be a +type+
655
675
  # column in the posts table.
656
676
  #
677
+ # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
678
+ # The +class_name+ of the +attachable+ is passed as a String.
679
+ #
657
680
  # class Asset < ActiveRecord::Base
658
681
  # belongs_to :attachable, polymorphic: true
659
682
  #
660
- # def attachable_type=(klass)
661
- # super(klass.to_s.classify.constantize.base_class.to_s)
683
+ # def attachable_type=(class_name)
684
+ # super(class_name.constantize.base_class.to_s)
662
685
  # end
663
686
  # end
664
687
  #
@@ -750,6 +773,12 @@ module ActiveRecord
750
773
  # like this can have unintended consequences.
751
774
  # In the above example posts with no approved comments are not returned at all, because
752
775
  # the conditions apply to the SQL statement as a whole and not just to the association.
776
+ #
777
+ # If you want to load all posts (including posts with no approved comments) then write
778
+ # your own LEFT OUTER JOIN query using ON
779
+ #
780
+ # Post.joins('LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = true')
781
+ #
753
782
  # You must disambiguate column references for this fallback to happen, for example
754
783
  # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
755
784
  #
@@ -789,7 +818,7 @@ module ActiveRecord
789
818
  # For example if all the addressables are either of class Person or Company then a total
790
819
  # of 3 queries will be executed. The list of addressable types to load is determined on
791
820
  # the back of the addresses loaded. This is not supported if Active Record has to fallback
792
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
821
+ # to the previous implementation of eager loading and will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>.
793
822
  # The reason is that the parent model's type is a column value so its corresponding table
794
823
  # name cannot be put in the +FROM+/+JOIN+ clauses of that query.
795
824
  #
@@ -1024,7 +1053,7 @@ module ActiveRecord
1024
1053
  # An empty array is returned if none are found.
1025
1054
  # [collection<<(object, ...)]
1026
1055
  # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
1027
- # Note that this operation instantly fires update sql without waiting for the save or update call on the
1056
+ # Note that this operation instantly fires update SQL without waiting for the save or update call on the
1028
1057
  # parent object, unless the parent object is a new record.
1029
1058
  # [collection.delete(object, ...)]
1030
1059
  # Removes one or more objects from the collection by setting their foreign keys to +NULL+.
@@ -1060,10 +1089,10 @@ module ActiveRecord
1060
1089
  # [collection.size]
1061
1090
  # Returns the number of associated objects.
1062
1091
  # [collection.find(...)]
1063
- # Finds an associated object according to the same rules as ActiveRecord::Base.find.
1092
+ # Finds an associated object according to the same rules as <tt>ActiveRecord::Base.find</tt>.
1064
1093
  # [collection.exists?(...)]
1065
1094
  # Checks whether an associated object with the given conditions exists.
1066
- # Uses the same rules as ActiveRecord::Base.exists?.
1095
+ # Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>.
1067
1096
  # [collection.build(attributes = {}, ...)]
1068
1097
  # Returns one or more new objects of the collection type that have been instantiated
1069
1098
  # with +attributes+ and linked to this object through a foreign key, but have not yet
@@ -1082,7 +1111,7 @@ module ActiveRecord
1082
1111
  #
1083
1112
  # === Example
1084
1113
  #
1085
- # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
1114
+ # A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add:
1086
1115
  # * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>)
1087
1116
  # * <tt>Firm#clients<<</tt>
1088
1117
  # * <tt>Firm#clients.delete</tt>
@@ -1116,8 +1145,8 @@ module ActiveRecord
1116
1145
  # Controls what happens to the associated objects when
1117
1146
  # their owner is destroyed. Note that these are implemented as
1118
1147
  # callbacks, and Rails executes callbacks in order. Therefore, other
1119
- # similar callbacks may affect the :dependent behavior, and the
1120
- # :dependent behavior may affect other callbacks.
1148
+ # similar callbacks may affect the <tt>:dependent</tt> behavior, and the
1149
+ # <tt>:dependent</tt> behavior may affect other callbacks.
1121
1150
  #
1122
1151
  # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
1123
1152
  # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
@@ -1163,8 +1192,8 @@ module ActiveRecord
1163
1192
  # If true, always save the associated objects or destroy them if marked for destruction,
1164
1193
  # when saving the parent object. If false, never save or destroy the associated objects.
1165
1194
  # By default, only save associated objects that are new records. This option is implemented as a
1166
- # before_save callback. Because callbacks are run in the order they are defined, associated objects
1167
- # may need to be explicitly saved in any user-defined before_save callbacks.
1195
+ # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects
1196
+ # may need to be explicitly saved in any user-defined +before_save+ callbacks.
1168
1197
  #
1169
1198
  # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
1170
1199
  # [:inverse_of]
@@ -1183,13 +1212,14 @@ module ActiveRecord
1183
1212
  # has_many :reports, -> { readonly }
1184
1213
  # has_many :subscribers, through: :subscriptions, source: :user
1185
1214
  def has_many(name, scope = nil, options = {}, &extension)
1186
- Builder::HasMany.build(self, name, scope, options, &extension)
1215
+ reflection = Builder::HasMany.build(self, name, scope, options, &extension)
1216
+ Reflection.add_reflection self, name, reflection
1187
1217
  end
1188
1218
 
1189
1219
  # Specifies a one-to-one association with another class. This method should only be used
1190
1220
  # if the other class contains the foreign key. If the current class contains the foreign key,
1191
1221
  # then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview
1192
- # on when to use has_one and when to use belongs_to.
1222
+ # on when to use +has_one+ and when to use +belongs_to+.
1193
1223
  #
1194
1224
  # The following methods for retrieval and query of a single associated object will be added:
1195
1225
  #
@@ -1197,7 +1227,8 @@ module ActiveRecord
1197
1227
  # Returns the associated object. +nil+ is returned if none is found.
1198
1228
  # [association=(associate)]
1199
1229
  # Assigns the associate object, extracts the primary key, sets it as the foreign key,
1200
- # and saves the associate object.
1230
+ # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing
1231
+ # associated object when assigning a new one, even if the new one isn't saved to database.
1201
1232
  # [build_association(attributes = {})]
1202
1233
  # Returns a new object of the associated type that has been instantiated
1203
1234
  # with +attributes+ and linked to this object through a foreign key, but has not
@@ -1286,7 +1317,8 @@ module ActiveRecord
1286
1317
  # has_one :club, through: :membership
1287
1318
  # has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
1288
1319
  def has_one(name, scope = nil, options = {})
1289
- Builder::HasOne.build(self, name, scope, options)
1320
+ reflection = Builder::HasOne.build(self, name, scope, options)
1321
+ Reflection.add_reflection self, name, reflection
1290
1322
  end
1291
1323
 
1292
1324
  # Specifies a one-to-one association with another class. This method should only be used
@@ -1357,7 +1389,7 @@ module ActiveRecord
1357
1389
  # class is created and decremented when it's destroyed. This requires that a column
1358
1390
  # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
1359
1391
  # is used on the associate class (such as a Post class) - that is the migration for
1360
- # <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
1392
+ # <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> will
1361
1393
  # return the count cached, see note below). You can also specify a custom counter
1362
1394
  # cache column by providing a column name instead of a +true+/+false+ value to this
1363
1395
  # option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
@@ -1390,7 +1422,7 @@ module ActiveRecord
1390
1422
  # belongs_to :firm, foreign_key: "client_of"
1391
1423
  # belongs_to :person, primary_key: "name", foreign_key: "person_name"
1392
1424
  # belongs_to :author, class_name: "Person", foreign_key: "author_id"
1393
- # belongs_to :valid_coupon, ->(o) { where "discounts > #{o.payments_count}" },
1425
+ # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
1394
1426
  # class_name: "Coupon", foreign_key: "coupon_id"
1395
1427
  # belongs_to :attachable, polymorphic: true
1396
1428
  # belongs_to :project, readonly: true
@@ -1398,7 +1430,8 @@ module ActiveRecord
1398
1430
  # belongs_to :company, touch: true
1399
1431
  # belongs_to :company, touch: :employees_last_updated_at
1400
1432
  def belongs_to(name, scope = nil, options = {})
1401
- Builder::BelongsTo.build(self, name, scope, options)
1433
+ reflection = Builder::BelongsTo.build(self, name, scope, options)
1434
+ Reflection.add_reflection self, name, reflection
1402
1435
  end
1403
1436
 
1404
1437
  # Specifies a many-to-many relationship with another class. This associates two classes via an
@@ -1439,7 +1472,7 @@ module ActiveRecord
1439
1472
  # [collection<<(object, ...)]
1440
1473
  # Adds one or more objects to the collection by creating associations in the join table
1441
1474
  # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
1442
- # Note that this operation instantly fires update sql without waiting for the save or update call on the
1475
+ # Note that this operation instantly fires update SQL without waiting for the save or update call on the
1443
1476
  # parent object, unless the parent object is a new record.
1444
1477
  # [collection.delete(object, ...)]
1445
1478
  # Removes one or more objects from the collection by removing their associations from the join table.
@@ -1462,10 +1495,10 @@ module ActiveRecord
1462
1495
  # [collection.find(id)]
1463
1496
  # Finds an associated object responding to the +id+ and that
1464
1497
  # meets the condition that it has to be associated with this object.
1465
- # Uses the same rules as ActiveRecord::Base.find.
1498
+ # Uses the same rules as <tt>ActiveRecord::Base.find</tt>.
1466
1499
  # [collection.exists?(...)]
1467
1500
  # Checks whether an associated object with the given conditions exists.
1468
- # Uses the same rules as ActiveRecord::Base.exists?.
1501
+ # Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>.
1469
1502
  # [collection.build(attributes = {})]
1470
1503
  # Returns a new object of the collection type that has been instantiated
1471
1504
  # with +attributes+ and linked to this object through the join table, but has not yet been saved.
@@ -1535,7 +1568,48 @@ module ActiveRecord
1535
1568
  # has_and_belongs_to_many :categories, join_table: "prods_cats"
1536
1569
  # has_and_belongs_to_many :categories, -> { readonly }
1537
1570
  def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
1538
- Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension)
1571
+ if scope.is_a?(Hash)
1572
+ options = scope
1573
+ scope = nil
1574
+ end
1575
+
1576
+ habtm_reflection = ActiveRecord::Reflection::AssociationReflection.new(:has_and_belongs_to_many, name, scope, options, self)
1577
+
1578
+ builder = Builder::HasAndBelongsToMany.new name, self, options
1579
+
1580
+ join_model = builder.through_model
1581
+
1582
+ # FIXME: we should move this to the internal constants. Also people
1583
+ # should never directly access this constant so I'm not happy about
1584
+ # setting it.
1585
+ const_set join_model.name, join_model
1586
+
1587
+ middle_reflection = builder.middle_reflection join_model
1588
+
1589
+ Builder::HasMany.define_callbacks self, middle_reflection
1590
+ Reflection.add_reflection self, middle_reflection.name, middle_reflection
1591
+ middle_reflection.parent_reflection = [name.to_sym, habtm_reflection]
1592
+
1593
+ include Module.new {
1594
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
1595
+ def destroy_associations
1596
+ association(:#{middle_reflection.name}).delete_all(:delete_all)
1597
+ association(:#{name}).reset
1598
+ super
1599
+ end
1600
+ RUBY
1601
+ }
1602
+
1603
+ hm_options = {}
1604
+ hm_options[:through] = middle_reflection.name
1605
+ hm_options[:source] = join_model.right_reflection.name
1606
+
1607
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table].each do |k|
1608
+ hm_options[k] = options[k] if options.key? k
1609
+ end
1610
+
1611
+ has_many name, scope, hm_options, &extension
1612
+ self._reflections[name.to_sym].parent_reflection = [name.to_sym, habtm_reflection]
1539
1613
  end
1540
1614
  end
1541
1615
  end
@@ -1,8 +1,8 @@
1
+ require 'active_model/forbidden_attributes_protection'
1
2
 
2
3
  module ActiveRecord
3
4
  module AttributeAssignment
4
5
  extend ActiveSupport::Concern
5
- include ActiveModel::DeprecatedMassAssignmentSecurity
6
6
  include ActiveModel::ForbiddenAttributesProtection
7
7
 
8
8
  # Allows you to set all the attributes by passing in a hash of attributes with
@@ -12,6 +12,9 @@ module ActiveRecord
12
12
  # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
13
13
  # exception is raised.
14
14
  def assign_attributes(new_attributes)
15
+ if !new_attributes.respond_to?(:stringify_keys)
16
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
17
+ end
15
18
  return if new_attributes.blank?
16
19
 
17
20
  attributes = new_attributes.stringify_keys
@@ -40,11 +43,11 @@ module ActiveRecord
40
43
 
41
44
  def _assign_attribute(k, v)
42
45
  public_send("#{k}=", v)
43
- rescue NoMethodError
46
+ rescue NoMethodError, NameError
44
47
  if respond_to?("#{k}=")
45
48
  raise
46
49
  else
47
- raise UnknownAttributeError, "unknown attribute: #{k}"
50
+ raise UnknownAttributeError.new(self, k)
48
51
  end
49
52
  end
50
53
 
@@ -41,8 +41,9 @@ module ActiveRecord
41
41
  # task.read_attribute_before_type_cast('id') # => '1'
42
42
  # task.read_attribute('completed_on') # => Sun, 21 Oct 2012
43
43
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
44
+ # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
44
45
  def read_attribute_before_type_cast(attr_name)
45
- @attributes[attr_name]
46
+ @attributes[attr_name.to_s]
46
47
  end
47
48
 
48
49
  # Returns a hash of attributes before typecasting and deserialization.
@@ -14,24 +14,12 @@ module ActiveRecord
14
14
 
15
15
  class_attribute :partial_writes, instance_writer: false
16
16
  self.partial_writes = true
17
-
18
- def self.partial_updates=(v); self.partial_writes = v; end
19
- def self.partial_updates?; partial_writes?; end
20
- def self.partial_updates; partial_writes; end
21
-
22
- ActiveSupport::Deprecation.deprecate_methods(
23
- singleton_class,
24
- :partial_updates= => :partial_writes=,
25
- :partial_updates? => :partial_writes?,
26
- :partial_updates => :partial_writes
27
- )
28
17
  end
29
18
 
30
19
  # Attempts to +save+ the record and clears changed attributes if successful.
31
20
  def save(*)
32
21
  if status = super
33
- @previously_changed = changes
34
- @changed_attributes.clear
22
+ changes_applied
35
23
  end
36
24
  status
37
25
  end
@@ -39,49 +27,70 @@ module ActiveRecord
39
27
  # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
40
28
  def save!(*)
41
29
  super.tap do
42
- @previously_changed = changes
43
- @changed_attributes.clear
30
+ changes_applied
44
31
  end
45
32
  end
46
33
 
47
34
  # <tt>reload</tt> the record and clears changed attributes.
48
35
  def reload(*)
49
36
  super.tap do
50
- @previously_changed.clear
51
- @changed_attributes.clear
37
+ reset_changes
52
38
  end
53
39
  end
54
40
 
41
+ def initialize_dup(other) # :nodoc:
42
+ super
43
+ init_changed_attributes
44
+ end
45
+
55
46
  private
47
+ def initialize_internals_callback
48
+ super
49
+ init_changed_attributes
50
+ end
51
+
52
+ def init_changed_attributes
53
+ @changed_attributes = nil
54
+ # Intentionally avoid using #column_defaults since overridden defaults (as is done in
55
+ # optimistic locking) won't get written unless they get marked as changed
56
+ self.class.columns.each do |c|
57
+ attr, orig_value = c.name, c.default
58
+ changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
59
+ end
60
+ end
61
+
56
62
  # Wrap write_attribute to remember original attribute value.
57
63
  def write_attribute(attr, value)
58
64
  attr = attr.to_s
59
65
 
66
+ save_changed_attribute(attr, value)
67
+
68
+ super(attr, value)
69
+ end
70
+
71
+ def save_changed_attribute(attr, value)
60
72
  # The attribute already has an unsaved change.
61
73
  if attribute_changed?(attr)
62
- old = @changed_attributes[attr]
63
- @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
74
+ old = changed_attributes[attr]
75
+ changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
64
76
  else
65
77
  old = clone_attribute_value(:read_attribute, attr)
66
- @changed_attributes[attr] = old if _field_changed?(attr, old, value)
78
+ changed_attributes[attr] = old if _field_changed?(attr, old, value)
67
79
  end
68
-
69
- # Carry on.
70
- super(attr, value)
71
80
  end
72
81
 
73
- def update_record(*)
82
+ def _update_record(*)
74
83
  partial_writes? ? super(keys_for_partial_write) : super
75
84
  end
76
85
 
77
- def create_record(*)
86
+ def _create_record(*)
78
87
  partial_writes? ? super(keys_for_partial_write) : super
79
88
  end
80
89
 
81
90
  # Serialized attributes should always be written in case they've been
82
91
  # changed in place.
83
92
  def keys_for_partial_write
84
- changed | (attributes.keys & self.class.serialized_attributes.keys)
93
+ changed
85
94
  end
86
95
 
87
96
  def _field_changed?(attr, old, value)
@@ -15,6 +15,7 @@ module ActiveRecord
15
15
 
16
16
  # Returns the primary key value.
17
17
  def id
18
+ return unless self.class.primary_key
18
19
  sync_with_transaction_state
19
20
  read_attribute(self.class.primary_key)
20
21
  end
@@ -37,6 +38,12 @@ module ActiveRecord
37
38
  read_attribute_before_type_cast(self.class.primary_key)
38
39
  end
39
40
 
41
+ # Returns the primary key previous value.
42
+ def id_was
43
+ sync_with_transaction_state
44
+ attribute_was(self.class.primary_key)
45
+ end
46
+
40
47
  protected
41
48
 
42
49
  def attribute_method?(attr_name)
@@ -52,7 +59,7 @@ module ActiveRecord
52
59
  end
53
60
  end
54
61
 
55
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
62
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
56
63
 
57
64
  def dangerous_attribute_method?(method_name)
58
65
  super && !ID_ATTRIBUTE_METHODS.include?(method_name)