activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,32 +1,42 @@
1
1
  # -*- coding: utf-8 -*-
2
- require 'active_support/core_ext/object/blank'
3
2
 
4
3
  module ActiveRecord
5
4
  # = Active Record Relation
6
5
  class Relation
7
6
  JoinOperation = Struct.new(:relation, :join_class, :on)
8
- ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
9
- MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
10
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
7
+
8
+ MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
9
+ :order, :joins, :where, :having, :bind, :references,
10
+ :extending]
11
+
12
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
13
+ :reverse_order, :distinct, :create_with, :uniq]
14
+
15
+ VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
11
16
 
12
17
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
13
18
 
14
19
  attr_reader :table, :klass, :loaded
15
- attr_accessor :extensions, :default_scoped
20
+ attr_accessor :default_scoped
21
+ alias :model :klass
16
22
  alias :loaded? :loaded
17
23
  alias :default_scoped? :default_scoped
18
24
 
19
- def initialize(klass, table)
20
- @klass, @table = klass, table
21
-
25
+ def initialize(klass, table, values = {})
26
+ @klass = klass
27
+ @table = table
28
+ @values = values
22
29
  @implicit_readonly = nil
23
30
  @loaded = false
24
31
  @default_scoped = false
32
+ end
25
33
 
26
- SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
27
- (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
28
- @extensions = []
29
- @create_with_value = {}
34
+ def initialize_copy(other)
35
+ # This method is a hot spot, so for now, use Hash[] to dup the hash.
36
+ # https://bugs.ruby-lang.org/issues/7166
37
+ @values = Hash[@values]
38
+ @values[:bind] = @values[:bind].dup if @values.key? :bind
39
+ reset
30
40
  end
31
41
 
32
42
  def insert(values)
@@ -72,65 +82,124 @@ module ActiveRecord
72
82
  binds)
73
83
  end
74
84
 
85
+ # Initializes new record from relation while maintaining the current
86
+ # scope.
87
+ #
88
+ # Expects arguments in the same format as +Base.new+.
89
+ #
90
+ # users = User.where(name: 'DHH')
91
+ # user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
92
+ #
93
+ # You can also pass a block to new with the new record as argument:
94
+ #
95
+ # user = users.new { |user| user.name = 'Oscar' }
96
+ # user.name # => Oscar
75
97
  def new(*args, &block)
76
98
  scoping { @klass.new(*args, &block) }
77
99
  end
78
100
 
79
- def initialize_copy(other)
80
- @bind_values = @bind_values.dup
81
- reset
82
- end
83
-
84
101
  alias build new
85
102
 
103
+ # Tries to create a new record with the same scoped attributes
104
+ # defined in the relation. Returns the initialized object if validation fails.
105
+ #
106
+ # Expects arguments in the same format as +Base.create+.
107
+ #
108
+ # ==== Examples
109
+ # users = User.where(name: 'Oscar')
110
+ # users.create # #<User id: 3, name: "oscar", ...>
111
+ #
112
+ # users.create(name: 'fxn')
113
+ # users.create # #<User id: 4, name: "fxn", ...>
114
+ #
115
+ # users.create { |user| user.name = 'tenderlove' }
116
+ # # #<User id: 5, name: "tenderlove", ...>
117
+ #
118
+ # users.create(name: nil) # validation on name
119
+ # # #<User id: nil, name: nil, ...>
86
120
  def create(*args, &block)
87
121
  scoping { @klass.create(*args, &block) }
88
122
  end
89
123
 
124
+ # Similar to #create, but calls +create!+ on the base class. Raises
125
+ # an exception if a validation error occurs.
126
+ #
127
+ # Expects arguments in the same format as <tt>Base.create!</tt>.
90
128
  def create!(*args, &block)
91
129
  scoping { @klass.create!(*args, &block) }
92
130
  end
93
131
 
94
- # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
95
- #
96
- # Expects arguments in the same format as <tt>Base.create</tt>.
132
+ def first_or_create(attributes = nil, &block) # :nodoc:
133
+ first || create(attributes, &block)
134
+ end
135
+
136
+ def first_or_create!(attributes = nil, &block) # :nodoc:
137
+ first || create!(attributes, &block)
138
+ end
139
+
140
+ def first_or_initialize(attributes = nil, &block) # :nodoc:
141
+ first || new(attributes, &block)
142
+ end
143
+
144
+ # Finds the first record with the given attributes, or creates a record
145
+ # with the attributes if one is not found:
97
146
  #
98
- # ==== Examples
99
- # # Find the first user named Penélope or create a new one.
100
- # User.where(:first_name => 'Penélope').first_or_create
101
- # # => <User id: 1, first_name: 'Penélope', last_name: nil>
147
+ # # Find the first user named "Penélope" or create a new one.
148
+ # User.find_or_create_by(first_name: 'Penélope')
149
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
102
150
  #
103
- # # Find the first user named Penélope or create a new one.
151
+ # # Find the first user named "Penélope" or create a new one.
104
152
  # # We already have one so the existing record will be returned.
105
- # User.where(:first_name => 'Penélope').first_or_create
106
- # # => <User id: 1, first_name: 'Penélope', last_name: nil>
153
+ # User.find_or_create_by(first_name: 'Penélope')
154
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
107
155
  #
108
- # # Find the first user named Scarlett or create a new one with a particular last name.
109
- # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
110
- # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
156
+ # # Find the first user named "Scarlett" or create a new one with
157
+ # # a particular last name.
158
+ # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
159
+ # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
111
160
  #
112
- # # Find the first user named Scarlett or create a new one with a different last name.
113
- # # We already have one so the existing record will be returned.
114
- # User.where(:first_name => 'Scarlett').first_or_create do |user|
115
- # user.last_name = "O'Hara"
161
+ # This method accepts a block, which is passed down to +create+. The last example
162
+ # above can be alternatively written this way:
163
+ #
164
+ # # Find the first user named "Scarlett" or create a new one with a
165
+ # # different last name.
166
+ # User.find_or_create_by(first_name: 'Scarlett') do |user|
167
+ # user.last_name = 'Johansson'
116
168
  # end
117
- # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
118
- def first_or_create(attributes = nil, options = {}, &block)
119
- first || create(attributes, options, &block)
169
+ # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
170
+ #
171
+ # This method always returns a record, but if creation was attempted and
172
+ # failed due to validation errors it won't be persisted, you get what
173
+ # +create+ returns in such situation.
174
+ #
175
+ # Please note *this method is not atomic*, it runs first a SELECT, and if
176
+ # there are no results an INSERT is attempted. If there are other threads
177
+ # or processes there is a race condition between both calls and it could
178
+ # be the case that you end up with two similar records.
179
+ #
180
+ # Whether that is a problem or not depends on the logic of the
181
+ # application, but in the particular case in which rows have a UNIQUE
182
+ # constraint an exception may be raised, just retry:
183
+ #
184
+ # begin
185
+ # CreditAccount.find_or_create_by(user_id: user.id)
186
+ # rescue ActiveRecord::RecordNotUnique
187
+ # retry
188
+ # end
189
+ #
190
+ def find_or_create_by(attributes, &block)
191
+ find_by(attributes) || create(attributes, &block)
120
192
  end
121
193
 
122
- # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
123
- #
124
- # Expects arguments in the same format as <tt>Base.create!</tt>.
125
- def first_or_create!(attributes = nil, options = {}, &block)
126
- first || create!(attributes, options, &block)
194
+ # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception
195
+ # is raised if the created record is invalid.
196
+ def find_or_create_by!(attributes, &block)
197
+ find_by(attributes) || create!(attributes, &block)
127
198
  end
128
199
 
129
- # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
130
- #
131
- # Expects arguments in the same format as <tt>Base.new</tt>.
132
- def first_or_initialize(attributes = nil, options = {}, &block)
133
- first || new(attributes, options, &block)
200
+ # Like <tt>find_or_create_by</tt>, but calls <tt>new</tt> instead of <tt>create</tt>.
201
+ def find_or_initialize_by(attributes, &block)
202
+ find_by(attributes) || new(attributes, &block)
134
203
  end
135
204
 
136
205
  # Runs EXPLAIN on the query or queries triggered by this relation and
@@ -141,58 +210,16 @@ module ActiveRecord
141
210
  # are needed by the next ones when eager loading is going on.
142
211
  #
143
212
  # Please see further details in the
144
- # {Active Record Query Interface guide}[http://edgeguides.rubyonrails.org/active_record_querying.html#running-explain].
213
+ # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
145
214
  def explain
146
- _, queries = collecting_queries_for_explain { exec_queries }
147
- exec_explain(queries)
215
+ exec_explain(collecting_queries_for_explain { exec_queries })
148
216
  end
149
217
 
218
+ # Converts relation objects to Array.
150
219
  def to_a
151
- # We monitor here the entire execution rather than individual SELECTs
152
- # because from the point of view of the user fetching the records of a
153
- # relation is a single unit of work. You want to know if this call takes
154
- # too long, not if the individual queries take too long.
155
- #
156
- # It could be the case that none of the queries involved surpass the
157
- # threshold, and at the same time the sum of them all does. The user
158
- # should get a query plan logged in that case.
159
- logging_query_plan do
160
- exec_queries
161
- end
162
- end
163
-
164
- def exec_queries
165
- return @records if loaded?
166
-
167
- default_scoped = with_default_scope
168
-
169
- if default_scoped.equal?(self)
170
- @records = if @readonly_value.nil? && !@klass.locking_enabled?
171
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
172
- else
173
- IdentityMap.without do
174
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
175
- end
176
- end
177
-
178
- preload = @preload_values
179
- preload += @includes_values unless eager_loading?
180
- preload.each do |associations|
181
- ActiveRecord::Associations::Preloader.new(@records, associations).run
182
- end
183
-
184
- # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
185
- # are JOINS and no explicit SELECT.
186
- readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
187
- @records.each { |record| record.readonly! } if readonly
188
- else
189
- @records = default_scoped.to_a
190
- end
191
-
192
- @loaded = true
220
+ load
193
221
  @records
194
222
  end
195
- private :exec_queries
196
223
 
197
224
  def as_json(options = nil) #:nodoc:
198
225
  to_a.as_json(options)
@@ -211,6 +238,7 @@ module ActiveRecord
211
238
  c.respond_to?(:zero?) ? c.zero? : c.empty?
212
239
  end
213
240
 
241
+ # Returns true if there are any records.
214
242
  def any?
215
243
  if block_given?
216
244
  to_a.any? { |*block_args| yield(*block_args) }
@@ -219,26 +247,29 @@ module ActiveRecord
219
247
  end
220
248
  end
221
249
 
250
+ # Returns true if there is more than one record.
222
251
  def many?
223
252
  if block_given?
224
253
  to_a.many? { |*block_args| yield(*block_args) }
225
254
  else
226
- @limit_value ? to_a.many? : size > 1
255
+ limit_value ? to_a.many? : size > 1
227
256
  end
228
257
  end
229
258
 
230
259
  # Scope all queries to the current scope.
231
260
  #
232
- # ==== Example
233
- #
234
- # Comment.where(:post_id => 1).scoping do
235
- # Comment.first # SELECT * FROM comments WHERE post_id = 1
261
+ # Comment.where(post_id: 1).scoping do
262
+ # Comment.first
236
263
  # end
264
+ # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
237
265
  #
238
266
  # Please check unscoped if you want to remove all previous scopes (including
239
267
  # the default_scope) during the execution of a block.
240
268
  def scoping
241
- @klass.with_scope(self, :overwrite) { yield }
269
+ previous, klass.current_scope = klass.current_scope, self
270
+ yield
271
+ ensure
272
+ klass.current_scope = previous
242
273
  end
243
274
 
244
275
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -249,50 +280,35 @@ module ActiveRecord
249
280
  # ==== Parameters
250
281
  #
251
282
  # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
252
- # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement.
253
- # See conditions in the intro.
254
- # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
255
283
  #
256
284
  # ==== Examples
257
285
  #
258
286
  # # Update all customers with the given attributes
259
- # Customer.update_all :wants_email => true
287
+ # Customer.update_all wants_email: true
260
288
  #
261
289
  # # Update all books with 'Rails' in their title
262
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
263
- #
264
- # # Update all avatars migrated more than a week ago
265
- # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
290
+ # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
266
291
  #
267
292
  # # Update all books that match conditions, but limit it to 5 ordered by date
268
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
269
- #
270
- # # Conditions from the current relation also works
271
- # Book.where('title LIKE ?', '%Rails%').update_all(:author => 'David')
272
- #
273
- # # The same idea applies to limit and order
274
- # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
275
- def update_all(updates, conditions = nil, options = {})
276
- IdentityMap.repository[symbolized_base_class].clear if IdentityMap.enabled?
277
- if conditions || options.present?
278
- where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
279
- else
280
- stmt = Arel::UpdateManager.new(arel.engine)
293
+ # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
294
+ def update_all(updates)
295
+ raise ArgumentError, "Empty list of attributes to change" if updates.blank?
281
296
 
282
- stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
283
- stmt.table(table)
284
- stmt.key = table[primary_key]
297
+ stmt = Arel::UpdateManager.new(arel.engine)
285
298
 
286
- if joins_values.any?
287
- @klass.connection.join_to_update(stmt, arel)
288
- else
289
- stmt.take(arel.limit)
290
- stmt.order(*arel.orders)
291
- stmt.wheres = arel.constraints
292
- end
299
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
300
+ stmt.table(table)
301
+ stmt.key = table[primary_key]
293
302
 
294
- @klass.connection.update stmt, 'SQL', bind_values
303
+ if with_default_scope.joins_values.any?
304
+ @klass.connection.join_to_update(stmt, arel)
305
+ else
306
+ stmt.take(arel.limit)
307
+ stmt.order(*arel.orders)
308
+ stmt.wheres = arel.constraints
295
309
  end
310
+
311
+ @klass.connection.update stmt, 'SQL', bind_values
296
312
  end
297
313
 
298
314
  # Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -306,28 +322,26 @@ module ActiveRecord
306
322
  # ==== Examples
307
323
  #
308
324
  # # Updates one record
309
- # Person.update(15, :user_name => 'Samuel', :group => 'expert')
325
+ # Person.update(15, user_name: 'Samuel', group: 'expert')
310
326
  #
311
327
  # # Updates multiple records
312
328
  # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
313
329
  # Person.update(people.keys, people.values)
314
330
  def update(id, attributes)
315
331
  if id.is_a?(Array)
316
- id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
332
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
317
333
  else
318
334
  object = find(id)
319
- object.update_attributes(attributes)
335
+ object.update(attributes)
320
336
  object
321
337
  end
322
338
  end
323
339
 
324
340
  # Destroys the records matching +conditions+ by instantiating each
325
341
  # record and calling its +destroy+ method. Each object's callbacks are
326
- # executed (including <tt>:dependent</tt> association options and
327
- # +before_destroy+/+after_destroy+ Observer methods). Returns the
342
+ # executed (including <tt>:dependent</tt> association options). Returns the
328
343
  # collection of objects that were destroyed; each will be frozen, to
329
- # reflect that no changes should be made (since they can't be
330
- # persisted).
344
+ # reflect that no changes should be made (since they can't be persisted).
331
345
  #
332
346
  # Note: Instantiation, callback execution, and deletion of each
333
347
  # record can be time consuming when you're removing many records at
@@ -346,8 +360,8 @@ module ActiveRecord
346
360
  # ==== Examples
347
361
  #
348
362
  # Person.destroy_all("last_login < '2004-04-04'")
349
- # Person.destroy_all(:status => "inactive")
350
- # Person.where(:age => 0..18).destroy_all
363
+ # Person.destroy_all(status: "inactive")
364
+ # Person.where(age: 0..18).destroy_all
351
365
  def destroy_all(conditions = nil)
352
366
  if conditions
353
367
  where(conditions).destroy_all
@@ -356,7 +370,7 @@ module ActiveRecord
356
370
  end
357
371
  end
358
372
 
359
- # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
373
+ # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
360
374
  # therefore all callbacks and filters are fired off before the object is deleted. This method is
361
375
  # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
362
376
  #
@@ -383,34 +397,41 @@ module ActiveRecord
383
397
  end
384
398
  end
385
399
 
386
- # Deletes the records matching +conditions+ without instantiating the records first, and hence not
387
- # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
388
- # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
389
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
390
- # the number of rows affected.
391
- #
392
- # ==== Parameters
393
- #
394
- # * +conditions+ - Conditions are specified the same way as with +find+ method.
395
- #
396
- # ==== Example
400
+ # Deletes the records matching +conditions+ without instantiating the records
401
+ # first, and hence not calling the +destroy+ method nor invoking callbacks. This
402
+ # is a single SQL DELETE statement that goes straight to the database, much more
403
+ # efficient than +destroy_all+. Be careful with relations though, in particular
404
+ # <tt>:dependent</tt> rules defined on associations are not honored. Returns the
405
+ # number of rows affected.
397
406
  #
398
407
  # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
399
408
  # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
400
- # Post.where(:person_id => 5).where(:category => ['Something', 'Else']).delete_all
409
+ # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
401
410
  #
402
411
  # Both calls delete the affected posts all at once with a single DELETE statement.
403
412
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
404
413
  # +after_destroy+ callbacks, use the +destroy_all+ method instead.
414
+ #
415
+ # If a limit scope is supplied, +delete_all+ raises an ActiveRecord error:
416
+ #
417
+ # Post.limit(100).delete_all
418
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope
405
419
  def delete_all(conditions = nil)
406
420
  raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
407
421
 
408
- IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
409
422
  if conditions
410
423
  where(conditions).delete_all
411
424
  else
412
- statement = arel.compile_delete
413
- affected = @klass.connection.delete(statement, 'SQL', bind_values)
425
+ stmt = Arel::DeleteManager.new(arel.engine)
426
+ stmt.from(table)
427
+
428
+ if with_default_scope.joins_values.any?
429
+ @klass.connection.join_to_delete(stmt, arel, table[primary_key])
430
+ else
431
+ stmt.wheres = arel.constraints
432
+ end
433
+
434
+ affected = @klass.connection.delete(stmt, 'SQL', bind_values)
414
435
 
415
436
  reset
416
437
  affected
@@ -420,8 +441,7 @@ module ActiveRecord
420
441
  # Deletes the row with a primary key matching the +id+ argument, using a
421
442
  # SQL +DELETE+ statement, and returns the number of rows deleted. Active
422
443
  # Record objects are not instantiated, so the object's callbacks are not
423
- # executed, including any <tt>:dependent</tt> association options or
424
- # Observer methods.
444
+ # executed, including any <tt>:dependent</tt> association options.
425
445
  #
426
446
  # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
427
447
  #
@@ -438,14 +458,25 @@ module ActiveRecord
438
458
  # # Delete multiple rows
439
459
  # Todo.delete([2,3,4])
440
460
  def delete(id_or_array)
441
- IdentityMap.remove_by_id(self.symbolized_base_class, id_or_array) if IdentityMap.enabled?
442
461
  where(primary_key => id_or_array).delete_all
443
462
  end
444
463
 
464
+ # Causes the records to be loaded from the database if they have not
465
+ # been loaded already. You can use this if for some reason you need
466
+ # to explicitly load some records before actually using them. The
467
+ # return value is the relation itself, not the records.
468
+ #
469
+ # Post.where(published: true).load # => #<ActiveRecord::Relation>
470
+ def load
471
+ exec_queries unless loaded?
472
+
473
+ self
474
+ end
475
+
476
+ # Forces reloading of relation.
445
477
  def reload
446
478
  reset
447
- to_a # force reload
448
- self
479
+ load
449
480
  end
450
481
 
451
482
  def reset
@@ -455,38 +486,61 @@ module ActiveRecord
455
486
  self
456
487
  end
457
488
 
489
+ # Returns sql statement for the relation.
490
+ #
491
+ # User.where(name: 'Oscar').to_sql
492
+ # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
458
493
  def to_sql
459
- @to_sql ||= klass.connection.to_sql(arel, @bind_values.dup)
494
+ @to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
460
495
  end
461
496
 
462
- def where_values_hash
497
+ # Returns a hash of where conditions.
498
+ #
499
+ # User.where(name: 'Oscar').where_values_hash
500
+ # # => {name: "Oscar"}
501
+ def where_values_hash(relation_table_name = table_name)
463
502
  equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
464
- node.left.relation.name == table_name
503
+ node.left.relation.name == relation_table_name
465
504
  }
466
505
 
467
- Hash[equalities.map { |where| [where.left.name, where.right] }].with_indifferent_access
506
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
507
+
508
+ Hash[equalities.map { |where|
509
+ name = where.left.name
510
+ [name, binds.fetch(name.to_s) { where.right }]
511
+ }]
468
512
  end
469
513
 
470
514
  def scope_for_create
471
515
  @scope_for_create ||= where_values_hash.merge(create_with_value)
472
516
  end
473
517
 
518
+ # Returns true if relation needs eager loading.
474
519
  def eager_loading?
475
520
  @should_eager_load ||=
476
- @eager_load_values.any? ||
477
- @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
521
+ eager_load_values.any? ||
522
+ includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
478
523
  end
479
524
 
480
525
  # Joins that are also marked for preloading. In which case we should just eager load them.
481
526
  # Note that this is a naive implementation because we could have strings and symbols which
482
527
  # represent the same association, but that aren't matched by this. Also, we could have
483
- # nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] }
528
+ # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
484
529
  def joined_includes_values
485
- @includes_values & @joins_values
530
+ includes_values & joins_values
531
+ end
532
+
533
+ # +uniq+ and +uniq!+ are silently deprecated. +uniq_value+ delegates to +distinct_value+
534
+ # to maintain backwards compatibility. Use +distinct_value+ instead.
535
+ def uniq_value
536
+ distinct_value
486
537
  end
487
538
 
539
+ # Compares two relations for equality.
488
540
  def ==(other)
489
541
  case other
542
+ when Associations::CollectionProxy, AssociationRelation
543
+ self == other.to_a
490
544
  when Relation
491
545
  other.to_sql == to_sql
492
546
  when Array
@@ -494,8 +548,8 @@ module ActiveRecord
494
548
  end
495
549
  end
496
550
 
497
- def inspect
498
- to_a.inspect
551
+ def pretty_print(q)
552
+ q.pp(self.to_a)
499
553
  end
500
554
 
501
555
  def with_default_scope #:nodoc:
@@ -508,8 +562,48 @@ module ActiveRecord
508
562
  end
509
563
  end
510
564
 
565
+ # Returns true if relation is blank.
566
+ def blank?
567
+ to_a.blank?
568
+ end
569
+
570
+ def values
571
+ Hash[@values]
572
+ end
573
+
574
+ def inspect
575
+ entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
576
+ entries[10] = '...' if entries.size == 11
577
+
578
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
579
+ end
580
+
511
581
  private
512
582
 
583
+ def exec_queries
584
+ default_scoped = with_default_scope
585
+
586
+ if default_scoped.equal?(self)
587
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
588
+
589
+ preload = preload_values
590
+ preload += includes_values unless eager_loading?
591
+ preload.each do |associations|
592
+ ActiveRecord::Associations::Preloader.new(@records, associations).run
593
+ end
594
+
595
+ # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
596
+ # are JOINS and no explicit SELECT.
597
+ readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
598
+ @records.each { |record| record.readonly! } if readonly
599
+ else
600
+ @records = default_scoped.to_a
601
+ end
602
+
603
+ @loaded = true
604
+ @records
605
+ end
606
+
513
607
  def references_eager_loaded_tables?
514
608
  joined_tables = arel.join_sources.map do |join|
515
609
  if join.is_a?(Arel::Nodes::StringJoin)
@@ -523,8 +617,34 @@ module ActiveRecord
523
617
 
524
618
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
525
619
  joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
526
-
527
- (tables_in_string(to_sql) - joined_tables).any?
620
+ string_tables = tables_in_string(to_sql)
621
+
622
+ if (references_values - joined_tables).any?
623
+ true
624
+ elsif !ActiveRecord::Base.disable_implicit_join_references &&
625
+ (string_tables - joined_tables).any?
626
+ ActiveSupport::Deprecation.warn(
627
+ "It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \
628
+ "that are referenced in a string SQL snippet. For example: \n" \
629
+ "\n" \
630
+ " Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
631
+ "\n" \
632
+ "Currently, Active Record recognizes the table in the string, and knows to JOIN the " \
633
+ "comments table to the query, rather than loading comments in a separate query. " \
634
+ "However, doing this without writing a full-blown SQL parser is inherently flawed. " \
635
+ "Since we don't want to write an SQL parser, we are removing this functionality. " \
636
+ "From now on, you must explicitly tell Active Record when you are referencing a table " \
637
+ "from a string:\n" \
638
+ "\n" \
639
+ " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \
640
+ "\n" \
641
+ "If you don't rely on implicit join references you can disable the feature entirely " \
642
+ "by setting `config.active_record.disable_implicit_join_references = true`."
643
+ )
644
+ true
645
+ else
646
+ false
647
+ end
528
648
  end
529
649
 
530
650
  def tables_in_string(string)