activerecord 4.0.13 → 4.1.0.beta1

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +745 -2700
  3. data/README.rdoc +2 -2
  4. data/examples/performance.rb +30 -18
  5. data/examples/simple.rb +4 -4
  6. data/lib/active_record.rb +2 -6
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +0 -4
  9. data/lib/active_record/associations.rb +87 -43
  10. data/lib/active_record/associations/alias_tracker.rb +1 -3
  11. data/lib/active_record/associations/association.rb +8 -16
  12. data/lib/active_record/associations/association_scope.rb +5 -16
  13. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  15. data/lib/active_record/associations/builder/association.rb +78 -54
  16. data/lib/active_record/associations/builder/belongs_to.rb +91 -58
  17. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
  19. data/lib/active_record/associations/builder/has_many.rb +2 -2
  20. data/lib/active_record/associations/builder/has_one.rb +5 -7
  21. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  22. data/lib/active_record/associations/collection_association.rb +68 -105
  23. data/lib/active_record/associations/collection_proxy.rb +12 -15
  24. data/lib/active_record/associations/has_many_association.rb +11 -9
  25. data/lib/active_record/associations/has_many_through_association.rb +16 -12
  26. data/lib/active_record/associations/has_one_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +204 -165
  28. data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
  29. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  30. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  31. data/lib/active_record/associations/join_helper.rb +2 -11
  32. data/lib/active_record/associations/preloader.rb +89 -34
  33. data/lib/active_record/associations/preloader/association.rb +43 -25
  34. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  38. data/lib/active_record/associations/singular_association.rb +6 -5
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +5 -2
  41. data/lib/active_record/attribute_methods.rb +45 -40
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +8 -22
  44. data/lib/active_record/attribute_methods/primary_key.rb +1 -7
  45. data/lib/active_record/attribute_methods/read.rb +55 -28
  46. data/lib/active_record/attribute_methods/serialization.rb +12 -33
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
  48. data/lib/active_record/attribute_methods/write.rb +37 -12
  49. data/lib/active_record/autosave_association.rb +207 -207
  50. data/lib/active_record/base.rb +5 -1
  51. data/lib/active_record/callbacks.rb +2 -2
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -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 +52 -83
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
  76. data/lib/active_record/connection_handling.rb +2 -2
  77. data/lib/active_record/core.rb +22 -43
  78. data/lib/active_record/counter_cache.rb +7 -7
  79. data/lib/active_record/enum.rb +100 -0
  80. data/lib/active_record/errors.rb +10 -5
  81. data/lib/active_record/fixture_set/file.rb +2 -1
  82. data/lib/active_record/fixtures.rb +171 -74
  83. data/lib/active_record/inheritance.rb +16 -22
  84. data/lib/active_record/integration.rb +52 -1
  85. data/lib/active_record/locking/optimistic.rb +7 -2
  86. data/lib/active_record/locking/pessimistic.rb +1 -1
  87. data/lib/active_record/log_subscriber.rb +5 -12
  88. data/lib/active_record/migration.rb +62 -46
  89. data/lib/active_record/migration/command_recorder.rb +7 -13
  90. data/lib/active_record/model_schema.rb +7 -14
  91. data/lib/active_record/nested_attributes.rb +10 -8
  92. data/lib/active_record/no_touching.rb +52 -0
  93. data/lib/active_record/null_relation.rb +3 -3
  94. data/lib/active_record/persistence.rb +16 -34
  95. data/lib/active_record/querying.rb +14 -12
  96. data/lib/active_record/railtie.rb +0 -50
  97. data/lib/active_record/railties/databases.rake +12 -15
  98. data/lib/active_record/readonly_attributes.rb +0 -6
  99. data/lib/active_record/reflection.rb +189 -75
  100. data/lib/active_record/relation.rb +69 -94
  101. data/lib/active_record/relation/batches.rb +57 -23
  102. data/lib/active_record/relation/calculations.rb +36 -43
  103. data/lib/active_record/relation/delegation.rb +54 -39
  104. data/lib/active_record/relation/finder_methods.rb +107 -62
  105. data/lib/active_record/relation/merger.rb +7 -20
  106. data/lib/active_record/relation/predicate_builder.rb +57 -38
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  109. data/lib/active_record/relation/query_methods.rb +110 -98
  110. data/lib/active_record/relation/spawn_methods.rb +1 -2
  111. data/lib/active_record/result.rb +45 -6
  112. data/lib/active_record/runtime_registry.rb +5 -0
  113. data/lib/active_record/sanitization.rb +6 -8
  114. data/lib/active_record/schema_dumper.rb +16 -5
  115. data/lib/active_record/schema_migration.rb +24 -25
  116. data/lib/active_record/scoping/default.rb +5 -18
  117. data/lib/active_record/scoping/named.rb +8 -29
  118. data/lib/active_record/store.rb +56 -28
  119. data/lib/active_record/tasks/database_tasks.rb +8 -4
  120. data/lib/active_record/timestamp.rb +4 -4
  121. data/lib/active_record/transactions.rb +8 -10
  122. data/lib/active_record/validations/presence.rb +1 -1
  123. data/lib/active_record/validations/uniqueness.rb +1 -6
  124. data/lib/active_record/version.rb +1 -1
  125. data/lib/rails/generators/active_record.rb +2 -8
  126. data/lib/rails/generators/active_record/migration.rb +18 -0
  127. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  128. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  129. metadata +32 -45
  130. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  131. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  132. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  133. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  134. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  135. data/lib/active_record/test_case.rb +0 -102
@@ -1,3 +1,5 @@
1
+ # This class is inherited by the has_many and has_many_and_belongs_to_many association classes
2
+
1
3
  require 'active_record/associations'
2
4
 
3
5
  module ActiveRecord::Associations::Builder
@@ -6,67 +8,57 @@ module ActiveRecord::Associations::Builder
6
8
  CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
7
9
 
8
10
  def valid_options
9
- super + [:table_name, :finder_sql, :counter_sql, :before_add,
11
+ super + [:table_name, :before_add,
10
12
  :after_add, :before_remove, :after_remove, :extend]
11
13
  end
12
14
 
13
- attr_reader :block_extension, :extension_module
14
-
15
- def initialize(*args, &extension)
16
- super(*args)
17
- @block_extension = extension
18
- end
15
+ attr_reader :block_extension
19
16
 
20
- def build
21
- show_deprecation_warnings
22
- wrap_block_extension
23
- reflection = super
24
- CALLBACKS.each { |callback_name| define_callback(callback_name) }
25
- reflection
17
+ def initialize(model, name, scope, options)
18
+ super
19
+ @mod = nil
20
+ if block_given?
21
+ @mod = Module.new(&Proc.new)
22
+ @scope = wrap_scope @scope, @mod
23
+ end
26
24
  end
27
25
 
28
- def writable?
29
- true
26
+ def self.define_callbacks(model, reflection)
27
+ super
28
+ name = reflection.name
29
+ options = reflection.options
30
+ CALLBACKS.each { |callback_name|
31
+ define_callback(model, callback_name, name, options)
32
+ }
30
33
  end
31
34
 
32
- def show_deprecation_warnings
33
- [:finder_sql, :counter_sql].each do |name|
34
- if options.include? name
35
- ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using scopes).")
36
- end
35
+ def define_extensions(model)
36
+ if @mod
37
+ extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
38
+ model.parent.const_set(extension_module_name, @mod)
37
39
  end
38
40
  end
39
41
 
40
- def wrap_block_extension
41
- if block_extension
42
- @extension_module = mod = Module.new(&block_extension)
43
- silence_warnings do
44
- model.parent.const_set(extension_module_name, mod)
45
- end
46
-
47
- prev_scope = @scope
42
+ def self.define_callback(model, callback_name, name, options)
43
+ full_callback_name = "#{callback_name}_for_#{name}"
48
44
 
49
- if prev_scope
50
- @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
45
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
46
+ model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
47
+ callbacks = Array(options[callback_name.to_sym]).map do |callback|
48
+ case callback
49
+ when Symbol
50
+ ->(method, owner, record) { owner.send(callback, record) }
51
+ when Proc
52
+ ->(method, owner, record) { callback.call(owner, record) }
51
53
  else
52
- @scope = proc { extending(mod) }
54
+ ->(method, owner, record) { callback.send(method, owner, record) }
53
55
  end
54
56
  end
57
+ model.send "#{full_callback_name}=", callbacks
55
58
  end
56
59
 
57
- def extension_module_name
58
- @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
59
- end
60
-
61
- def define_callback(callback_name)
62
- full_callback_name = "#{callback_name}_for_#{name}"
63
-
64
- # TODO : why do i need method_defined? I think its because of the inheritance chain
65
- model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
66
- model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
67
- end
68
-
69
- def define_readers
60
+ # Defines the setter and getter methods for the collection_singular_ids.
61
+ def self.define_readers(mixin, name)
70
62
  super
71
63
 
72
64
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
@@ -76,7 +68,7 @@ module ActiveRecord::Associations::Builder
76
68
  CODE
77
69
  end
78
70
 
79
- def define_writers
71
+ def self.define_writers(mixin, name)
80
72
  super
81
73
 
82
74
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
@@ -85,5 +77,15 @@ module ActiveRecord::Associations::Builder
85
77
  end
86
78
  CODE
87
79
  end
80
+
81
+ private
82
+
83
+ def wrap_scope(scope, mod)
84
+ if scope
85
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
86
+ else
87
+ proc { extending(mod) }
88
+ end
89
+ end
88
90
  end
89
91
  end
@@ -1,39 +1,121 @@
1
1
  module ActiveRecord::Associations::Builder
2
- class HasAndBelongsToMany < CollectionAssociation #:nodoc:
3
- def macro
4
- :has_and_belongs_to_many
5
- end
2
+ class HasAndBelongsToMany # :nodoc:
3
+ class JoinTableResolver
4
+ KnownTable = Struct.new :join_table
5
+
6
+ class KnownClass
7
+ def initialize(lhs_class, rhs_class_name)
8
+ @lhs_class = lhs_class
9
+ @rhs_class_name = rhs_class_name
10
+ @join_table = nil
11
+ end
12
+
13
+ def join_table
14
+ @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
15
+ end
16
+
17
+ private
18
+ def klass; @rhs_class_name.constantize; end
19
+ end
6
20
 
7
- def valid_options
8
- super + [:join_table, :association_foreign_key, :delete_sql, :insert_sql]
21
+ def self.build(lhs_class, name, options)
22
+ if options[:join_table]
23
+ KnownTable.new options[:join_table].to_s
24
+ else
25
+ class_name = options.fetch(:class_name) {
26
+ name.to_s.camelize.singularize
27
+ }
28
+ KnownClass.new lhs_class, class_name
29
+ end
30
+ end
9
31
  end
10
32
 
11
- def build
12
- reflection = super
13
- define_destroy_hook
14
- reflection
33
+ attr_reader :lhs_model, :association_name, :options
34
+
35
+ def initialize(association_name, lhs_model, options)
36
+ @association_name = association_name
37
+ @lhs_model = lhs_model
38
+ @options = options
15
39
  end
16
40
 
17
- def show_deprecation_warnings
18
- super
41
+ def through_model
42
+ habtm = JoinTableResolver.build lhs_model, association_name, options
43
+
44
+ join_model = Class.new(ActiveRecord::Base) {
45
+ class << self;
46
+ attr_accessor :class_resolver
47
+ attr_accessor :name
48
+ attr_accessor :table_name_resolver
49
+ attr_accessor :left_reflection
50
+ attr_accessor :right_reflection
51
+ end
52
+
53
+ def self.table_name
54
+ table_name_resolver.join_table
55
+ end
56
+
57
+ def self.compute_type(class_name)
58
+ class_resolver.compute_type class_name
59
+ end
19
60
 
20
- [:delete_sql, :insert_sql].each do |name|
21
- if options.include? name
22
- ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using has_many :through).")
61
+ def self.add_left_association(name, options)
62
+ belongs_to name, options
63
+ self.left_reflection = reflect_on_association(name)
23
64
  end
65
+
66
+ def self.add_right_association(name, options)
67
+ rhs_name = name.to_s.singularize.to_sym
68
+ belongs_to rhs_name, options
69
+ self.right_reflection = reflect_on_association(rhs_name)
70
+ end
71
+
72
+ }
73
+
74
+ join_model.name = "HABTM_#{association_name.to_s.camelize}"
75
+ join_model.table_name_resolver = habtm
76
+ join_model.class_resolver = lhs_model
77
+
78
+ join_model.add_left_association :left_side, class: lhs_model
79
+ join_model.add_right_association association_name, belongs_to_options(options)
80
+ join_model
81
+ end
82
+
83
+ def middle_reflection(join_model)
84
+ middle_name = [lhs_model.name.downcase.pluralize,
85
+ association_name].join('_').gsub(/::/, '_').to_sym
86
+ middle_options = middle_options join_model
87
+ hm_builder = HasMany.create_builder(lhs_model,
88
+ middle_name,
89
+ nil,
90
+ middle_options)
91
+ hm_builder.build lhs_model
92
+ end
93
+
94
+ private
95
+
96
+ def middle_options(join_model)
97
+ middle_options = {}
98
+ middle_options[:class] = join_model
99
+ middle_options[:source] = join_model.left_reflection.name
100
+ if options.key? :foreign_key
101
+ middle_options[:foreign_key] = options[:foreign_key]
24
102
  end
103
+ middle_options
25
104
  end
26
105
 
27
- def define_destroy_hook
28
- name = self.name
29
- model.send(:include, Module.new {
30
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
- def destroy_associations
32
- association(:#{name}).delete_all
33
- super
34
- end
35
- RUBY
36
- })
106
+ def belongs_to_options(options)
107
+ rhs_options = {}
108
+
109
+ if options.key? :class_name
110
+ rhs_options[:foreign_key] = options[:class_name].foreign_key
111
+ rhs_options[:class_name] = options[:class_name]
112
+ end
113
+
114
+ if options.key? :association_foreign_key
115
+ rhs_options[:foreign_key] = options[:association_foreign_key]
116
+ end
117
+
118
+ rhs_options
37
119
  end
38
120
  end
39
121
  end
@@ -8,8 +8,8 @@ module ActiveRecord::Associations::Builder
8
8
  super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
9
9
  end
10
10
 
11
- def valid_dependent_options
12
- [:destroy, :delete_all, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
13
13
  end
14
14
  end
15
15
  end
@@ -10,16 +10,14 @@ module ActiveRecord::Associations::Builder
10
10
  valid
11
11
  end
12
12
 
13
- def constructable?
14
- !options[:through]
13
+ def self.valid_dependent_options
14
+ [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
15
15
  end
16
16
 
17
- def configure_dependency
18
- super unless options[:through]
19
- end
17
+ private
20
18
 
21
- def valid_dependent_options
22
- [:destroy, :delete, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
19
+ def self.add_before_destroy_callbacks(model, reflection)
20
+ super unless reflection.options[:through]
23
21
  end
24
22
  end
25
23
  end
@@ -1,19 +1,18 @@
1
+ # This class is inherited by the has_one and belongs_to association classes
2
+
1
3
  module ActiveRecord::Associations::Builder
2
4
  class SingularAssociation < Association #:nodoc:
3
5
  def valid_options
4
6
  super + [:remote, :dependent, :primary_key, :inverse_of]
5
7
  end
6
8
 
7
- def constructable?
8
- true
9
- end
10
-
11
- def define_accessors
9
+ def self.define_accessors(model, reflection)
12
10
  super
13
- define_constructors if constructable?
11
+ define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
14
12
  end
15
13
 
16
- def define_constructors
14
+ # Defines the (build|create)_association methods for belongs_to or has_one association
15
+ def self.define_constructors(mixin, name)
17
16
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
18
17
  def build_#{name}(*args, &block)
19
18
  association(:#{name}).build(*args, &block)
@@ -7,7 +7,6 @@ module ActiveRecord
7
7
  # collections. See the class hierarchy in AssociationProxy.
8
8
  #
9
9
  # CollectionAssociation:
10
- # HasAndBelongsToManyAssociation => has_and_belongs_to_many
11
10
  # HasManyAssociation => has_many
12
11
  # HasManyThroughAssociation + ThroughAssociation => has_many :through
13
12
  #
@@ -34,13 +33,7 @@ module ActiveRecord
34
33
  reload
35
34
  end
36
35
 
37
- if owner.new_record?
38
- # Cache the proxy separately before the owner has an id
39
- # or else a post-save proxy will still lack the id
40
- @new_record_proxy ||= CollectionProxy.new(klass, self)
41
- else
42
- @proxy ||= CollectionProxy.new(klass, self)
43
- end
36
+ @proxy ||= CollectionProxy.create(klass, self)
44
37
  end
45
38
 
46
39
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -50,7 +43,7 @@ module ActiveRecord
50
43
 
51
44
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
52
45
  def ids_reader
53
- if loaded? || options[:finder_sql]
46
+ if loaded?
54
47
  load_target.map do |record|
55
48
  record.send(reflection.association_primary_key)
56
49
  end
@@ -85,12 +78,9 @@ module ActiveRecord
85
78
  if block_given?
86
79
  load_target.find(*args) { |*block_args| yield(*block_args) }
87
80
  else
88
- if options[:finder_sql]
89
- find_by_scan(*args)
90
- elsif options[:inverse_of] && loaded?
81
+ if options[:inverse_of] && loaded?
91
82
  args_flatten = args.flatten
92
83
  raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
93
-
94
84
  result = find_by_scan(*args)
95
85
 
96
86
  result_size = Array(result).size
@@ -124,11 +114,11 @@ module ActiveRecord
124
114
  end
125
115
 
126
116
  def create(attributes = {}, &block)
127
- _create_record(attributes, &block)
117
+ create_record(attributes, &block)
128
118
  end
129
119
 
130
120
  def create!(attributes = {}, &block)
131
- _create_record(attributes, true, &block)
121
+ create_record(attributes, true, &block)
132
122
  end
133
123
 
134
124
  # Add +records+ to this association. Returns +self+ so method calls may
@@ -159,11 +149,33 @@ module ActiveRecord
159
149
  end
160
150
  end
161
151
 
162
- # Remove all records from this association.
152
+ # Removes all records from the association without calling callbacks
153
+ # on the associated records. It honors the `:dependent` option. However
154
+ # if the `:dependent` value is `:destroy` then in that case the `:delete_all`
155
+ # deletion strategy for the association is applied.
156
+ #
157
+ # You can force a particular deletion strategy by passing a parameter.
158
+ #
159
+ # Example:
160
+ #
161
+ # @author.books.delete_all(:nullify)
162
+ # @author.books.delete_all(:delete_all)
163
163
  #
164
164
  # See delete for more info.
165
- def delete_all
166
- delete(:all).tap do
165
+ def delete_all(dependent = nil)
166
+ if dependent.present? && ![:nullify, :delete_all].include?(dependent)
167
+ raise ArgumentError, "Valid values are :nullify or :delete_all"
168
+ end
169
+
170
+ dependent = if dependent.present?
171
+ dependent
172
+ elsif options[:dependent] == :destroy
173
+ :delete_all
174
+ else
175
+ options[:dependent]
176
+ end
177
+
178
+ delete(:all, dependent: dependent).tap do
167
179
  reset
168
180
  loaded!
169
181
  end
@@ -179,36 +191,29 @@ module ActiveRecord
179
191
  end
180
192
  end
181
193
 
182
- # Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
183
- # association, it will be used for the query. Otherwise, construct options and pass them with
194
+ # Count all records using SQL. Construct options and pass them with
184
195
  # scope to the target class's +count+.
185
196
  def count(column_name = nil, count_options = {})
197
+ # TODO: Remove count_options argument as soon we remove support to
198
+ # activerecord-deprecated_finders.
186
199
  column_name, count_options = nil, column_name if column_name.is_a?(Hash)
187
200
 
188
- if options[:counter_sql] || options[:finder_sql]
189
- unless count_options.blank?
190
- raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
191
- end
192
-
193
- reflection.klass.count_by_sql(custom_counter_sql)
194
- else
195
- relation = scope
196
- if association_scope.distinct_value
197
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
198
- column_name ||= reflection.klass.primary_key
199
- relation = relation.distinct
200
- end
201
+ relation = scope
202
+ if association_scope.distinct_value
203
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
204
+ column_name ||= reflection.klass.primary_key
205
+ relation = relation.distinct
206
+ end
201
207
 
202
- value = relation.count(column_name, count_options)
208
+ value = relation.count(column_name)
203
209
 
204
- limit = options[:limit]
205
- offset = options[:offset]
210
+ limit = options[:limit]
211
+ offset = options[:offset]
206
212
 
207
- if limit || offset
208
- [ [value - offset.to_i, 0].max, limit.to_i ].min
209
- else
210
- value
211
- end
213
+ if limit || offset
214
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
215
+ else
216
+ value
212
217
  end
213
218
  end
214
219
 
@@ -220,19 +225,11 @@ module ActiveRecord
220
225
  # are actually removed from the database, that depends precisely on
221
226
  # +delete_records+. They are in any case removed from the collection.
222
227
  def delete(*records)
223
- dependent = options[:dependent]
228
+ _options = records.extract_options!
229
+ dependent = _options[:dependent] || options[:dependent]
224
230
 
225
231
  if records.first == :all
226
-
227
- if dependent && dependent == :destroy
228
- message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \
229
- 'It means if the :dependent option is :destroy then the associated ' \
230
- 'records would be deleted without loading and invoking callbacks.'
231
-
232
- ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message)
233
- end
234
-
235
- if (loaded? || dependent == :destroy) && dependent != :delete_all
232
+ if loaded? || dependent == :destroy
236
233
  delete_or_destroy(load_target, dependent)
237
234
  else
238
235
  delete_records(:all, dependent)
@@ -243,11 +240,11 @@ module ActiveRecord
243
240
  end
244
241
  end
245
242
 
246
- # Destroy +records+ and remove them from this association calling
247
- # +before_remove+ and +after_remove+ callbacks.
243
+ # Deletes the +records+ and removes them from this association calling
244
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
248
245
  #
249
- # Note that this method will _always_ remove records from the database
250
- # ignoring the +:dependent+ option.
246
+ # Note that this method removes records from the database ignoring the
247
+ # +:dependent+ option.
251
248
  def destroy(*records)
252
249
  records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
253
250
  delete_or_destroy(records, :destroy)
@@ -291,14 +288,14 @@ module ActiveRecord
291
288
 
292
289
  # Returns true if the collection is empty.
293
290
  #
294
- # If the collection has been loaded or the <tt>:counter_sql</tt> option
295
- # is provided, it is equivalent to <tt>collection.size.zero?</tt>. If the
291
+ # If the collection has been loaded
292
+ # it is equivalent to <tt>collection.size.zero?</tt>. If the
296
293
  # collection has not been loaded, it is equivalent to
297
294
  # <tt>collection.exists?</tt>. If the collection has not already been
298
295
  # loaded and you are going to fetch the records anyway it is better to
299
296
  # check <tt>collection.length.zero?</tt>.
300
297
  def empty?
301
- if loaded? || options[:counter_sql]
298
+ if loaded?
302
299
  size.zero?
303
300
  else
304
301
  @target.blank? && !scope.exists?
@@ -351,7 +348,6 @@ module ActiveRecord
351
348
  if record.new_record?
352
349
  include_in_memory?(record)
353
350
  else
354
- load_target if options[:finder_sql]
355
351
  loaded? ? target.include?(record) : scope.exists?(record)
356
352
  end
357
353
  else
@@ -368,8 +364,8 @@ module ActiveRecord
368
364
  target
369
365
  end
370
366
 
371
- def add_to_target(record)
372
- callback(:before_add, record)
367
+ def add_to_target(record, skip_callbacks = false)
368
+ callback(:before_add, record) unless skip_callbacks
373
369
  yield(record) if block_given?
374
370
 
375
371
  if association_scope.distinct_value && index = @target.index(record)
@@ -378,7 +374,7 @@ module ActiveRecord
378
374
  @target << record
379
375
  end
380
376
 
381
- callback(:after_add, record)
377
+ callback(:after_add, record) unless skip_callbacks
382
378
  set_inverse_instance(record)
383
379
 
384
380
  record
@@ -396,31 +392,8 @@ module ActiveRecord
396
392
 
397
393
  private
398
394
 
399
- def custom_counter_sql
400
- if options[:counter_sql]
401
- interpolate(options[:counter_sql])
402
- else
403
- # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
404
- interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
405
- count_with = $2.to_s
406
- count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
407
- "SELECT #{$1}COUNT(#{count_with}) FROM"
408
- end
409
- end
410
- end
411
-
412
- def custom_finder_sql
413
- interpolate(options[:finder_sql])
414
- end
415
-
416
395
  def find_target
417
- records =
418
- if options[:finder_sql]
419
- reflection.klass.find_by_sql(custom_finder_sql)
420
- else
421
- scope.to_a
422
- end
423
-
396
+ records = scope.to_a
424
397
  records.each { |record| set_inverse_instance(record) }
425
398
  records
426
399
  end
@@ -455,13 +428,13 @@ module ActiveRecord
455
428
  persisted + memory
456
429
  end
457
430
 
458
- def _create_record(attributes, raise = false, &block)
431
+ def create_record(attributes, raise = false, &block)
459
432
  unless owner.persisted?
460
433
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
461
434
  end
462
435
 
463
436
  if attributes.is_a?(Array)
464
- attributes.collect { |attr| _create_record(attr, raise, &block) }
437
+ attributes.collect { |attr| create_record(attr, raise, &block) }
465
438
  else
466
439
  transaction do
467
440
  add_to_target(build_record(attributes)) do |record|
@@ -535,20 +508,13 @@ module ActiveRecord
535
508
 
536
509
  def callback(method, record)
537
510
  callbacks_for(method).each do |callback|
538
- case callback
539
- when Symbol
540
- owner.send(callback, record)
541
- when Proc
542
- callback.call(owner, record)
543
- else
544
- callback.send(method, owner, record)
545
- end
511
+ callback.call(method, owner, record)
546
512
  end
547
513
  end
548
514
 
549
515
  def callbacks_for(callback_name)
550
516
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
551
- owner.class.send(full_callback_name.to_sym) || []
517
+ owner.class.send(full_callback_name)
552
518
  end
553
519
 
554
520
  # Should we deal with assoc.first or assoc.last by issuing an independent query to
@@ -559,24 +525,21 @@ module ActiveRecord
559
525
  # Otherwise, go to the database only if none of the following are true:
560
526
  # * target already loaded
561
527
  # * owner is new record
562
- # * custom :finder_sql exists
563
528
  # * target contains new or changed record(s)
564
- # * the first arg is an integer (which indicates the number of records to be returned)
565
529
  def fetch_first_or_last_using_find?(args)
566
530
  if args.first.is_a?(Hash)
567
531
  true
568
532
  else
569
533
  !(loaded? ||
570
534
  owner.new_record? ||
571
- options[:finder_sql] ||
572
- target.any? { |record| record.new_record? || record.changed? } ||
573
- args.first.kind_of?(Integer))
535
+ target.any? { |record| record.new_record? || record.changed? })
574
536
  end
575
537
  end
576
538
 
577
539
  def include_in_memory?(record)
578
540
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
579
- owner.send(reflection.through_reflection.name).any? { |source|
541
+ assoc = owner.association(reflection.through_reflection.name)
542
+ assoc.reader.any? { |source|
580
543
  target = source.send(reflection.source_reflection.name)
581
544
  target.respond_to?(:include?) ? target.include?(record) : target == record
582
545
  } || target.include?(record)
@@ -585,7 +548,7 @@ module ActiveRecord
585
548
  end
586
549
  end
587
550
 
588
- # If using a custom finder_sql or if the :inverse_of option has been
551
+ # If the :inverse_of option has been
589
552
  # specified, then #find scans the entire collection.
590
553
  def find_by_scan(*args)
591
554
  expects_array = args.first.kind_of?(Array)