activerecord 3.0.0 → 4.0.0

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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,82 +1,229 @@
1
- require 'active_support/core_ext/object/blank'
1
+ # -*- coding: utf-8 -*-
2
2
 
3
3
  module ActiveRecord
4
4
  # = Active Record Relation
5
5
  class Relation
6
6
  JoinOperation = Struct.new(:relation, :join_class, :on)
7
- ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
8
- MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
9
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
10
7
 
11
- include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
8
+ MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
9
+ :order, :joins, :where, :having, :bind, :references,
10
+ :extending]
12
11
 
13
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
14
- delegate :insert, :to => :arel
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
16
+
17
+ include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
15
18
 
16
19
  attr_reader :table, :klass, :loaded
17
- attr_accessor :extensions
20
+ attr_accessor :default_scoped, :proxy_association
21
+ alias :model :klass
18
22
  alias :loaded? :loaded
23
+ alias :default_scoped? :default_scoped
19
24
 
20
- def initialize(klass, table)
21
- @klass, @table = klass, table
22
-
25
+ def initialize(klass, table, values = {})
26
+ @klass = klass
27
+ @table = table
28
+ @values = values
23
29
  @implicit_readonly = nil
24
30
  @loaded = false
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 = []
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
29
40
  end
30
41
 
31
- def new(*args, &block)
32
- scoping { @klass.new(*args, &block) }
42
+ def insert(values)
43
+ primary_key_value = nil
44
+
45
+ if primary_key && Hash === values
46
+ primary_key_value = values[values.keys.find { |k|
47
+ k.name == primary_key
48
+ }]
49
+
50
+ if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
51
+ primary_key_value = connection.next_sequence_value(klass.sequence_name)
52
+ values[klass.arel_table[klass.primary_key]] = primary_key_value
53
+ end
54
+ end
55
+
56
+ im = arel.create_insert
57
+ im.into @table
58
+
59
+ conn = @klass.connection
60
+
61
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
62
+ binds = substitutes.map do |arel_attr, value|
63
+ [@klass.columns_hash[arel_attr.name], value]
64
+ end
65
+
66
+ substitutes.each_with_index do |tuple, i|
67
+ tuple[1] = conn.substitute_at(binds[i][0], i)
68
+ end
69
+
70
+ if values.empty? # empty insert
71
+ im.values = Arel.sql(connection.empty_insert_statement_value)
72
+ else
73
+ im.insert substitutes
74
+ end
75
+
76
+ conn.insert(
77
+ im,
78
+ 'SQL',
79
+ primary_key,
80
+ primary_key_value,
81
+ nil,
82
+ binds)
33
83
  end
34
84
 
35
- def initialize_copy(other)
36
- reset
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
97
+ def new(*args, &block)
98
+ scoping { @klass.new(*args, &block) }
37
99
  end
38
100
 
39
101
  alias build new
40
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, ...>
41
120
  def create(*args, &block)
42
121
  scoping { @klass.create(*args, &block) }
43
122
  end
44
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>.
45
128
  def create!(*args, &block)
46
129
  scoping { @klass.create!(*args, &block) }
47
130
  end
48
131
 
49
- def respond_to?(method, include_private = false)
50
- return true if arel.respond_to?(method, include_private) || Array.method_defined?(method) || @klass.respond_to?(method, include_private)
132
+ def first_or_create(attributes = nil, &block) # :nodoc:
133
+ first || create(attributes, &block)
134
+ end
51
135
 
52
- if match = DynamicFinderMatch.match(method)
53
- return true if @klass.send(:all_attributes_exists?, match.attribute_names)
54
- elsif match = DynamicScopeMatch.match(method)
55
- return true if @klass.send(:all_attributes_exists?, match.attribute_names)
56
- else
57
- super
58
- end
136
+ def first_or_create!(attributes = nil, &block) # :nodoc:
137
+ first || create!(attributes, &block)
59
138
  end
60
139
 
61
- def to_a
62
- return @records if loaded?
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:
146
+ #
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>
150
+ #
151
+ # # Find the first user named "Penélope" or create a new one.
152
+ # # We already have one so the existing record will be returned.
153
+ # User.find_or_create_by(first_name: 'Penélope')
154
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
155
+ #
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">
160
+ #
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'
168
+ # end
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)
192
+ end
63
193
 
64
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql)
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)
198
+ end
65
199
 
66
- preload = @preload_values
67
- preload += @includes_values unless eager_loading?
68
- preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
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)
203
+ end
69
204
 
70
- # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
71
- # are JOINS and no explicit SELECT.
72
- readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
73
- @records.each { |record| record.readonly! } if readonly
205
+ # Runs EXPLAIN on the query or queries triggered by this relation and
206
+ # returns the result as a string. The string is formatted imitating the
207
+ # ones printed by the database shell.
208
+ #
209
+ # Note that this method actually runs the queries, since the results of some
210
+ # are needed by the next ones when eager loading is going on.
211
+ #
212
+ # Please see further details in the
213
+ # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
214
+ def explain
215
+ exec_explain(collecting_queries_for_explain { exec_queries })
216
+ end
74
217
 
75
- @loaded = true
218
+ # Converts relation objects to Array.
219
+ def to_a
220
+ load
76
221
  @records
77
222
  end
78
223
 
79
- def as_json(options = nil) to_a end #:nodoc:
224
+ def as_json(options = nil) #:nodoc:
225
+ to_a.as_json(options)
226
+ end
80
227
 
81
228
  # Returns size of the records.
82
229
  def size
@@ -85,9 +232,13 @@ module ActiveRecord
85
232
 
86
233
  # Returns true if there are no records.
87
234
  def empty?
88
- loaded? ? @records.empty? : count.zero?
235
+ return @records.empty? if loaded?
236
+
237
+ c = count
238
+ c.respond_to?(:zero?) ? c.zero? : c.empty?
89
239
  end
90
240
 
241
+ # Returns true if there are any records.
91
242
  def any?
92
243
  if block_given?
93
244
  to_a.any? { |*block_args| yield(*block_args) }
@@ -96,31 +247,29 @@ module ActiveRecord
96
247
  end
97
248
  end
98
249
 
250
+ # Returns true if there is more than one record.
99
251
  def many?
100
252
  if block_given?
101
253
  to_a.many? { |*block_args| yield(*block_args) }
102
254
  else
103
- @limit_value ? to_a.many? : size > 1
255
+ limit_value ? to_a.many? : size > 1
104
256
  end
105
257
  end
106
258
 
107
259
  # Scope all queries to the current scope.
108
260
  #
109
- # ==== Example
110
- #
111
- # Comment.where(:post_id => 1).scoping do
112
- # Comment.first # SELECT * FROM comments WHERE post_id = 1
261
+ # Comment.where(post_id: 1).scoping do
262
+ # Comment.first
113
263
  # end
264
+ # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
114
265
  #
115
266
  # Please check unscoped if you want to remove all previous scopes (including
116
267
  # the default_scope) during the execution of a block.
117
268
  def scoping
118
- @klass.scoped_methods << self
119
- begin
120
- yield
121
- ensure
122
- @klass.scoped_methods.pop
123
- end
269
+ previous, klass.current_scope = klass.current_scope, self
270
+ yield
271
+ ensure
272
+ klass.current_scope = previous
124
273
  end
125
274
 
126
275
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -131,34 +280,35 @@ module ActiveRecord
131
280
  # ==== Parameters
132
281
  #
133
282
  # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
134
- # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement.
135
- # See conditions in the intro.
136
- # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
137
283
  #
138
284
  # ==== Examples
139
285
  #
140
286
  # # Update all customers with the given attributes
141
- # Customer.update_all :wants_email => true
287
+ # Customer.update_all wants_email: true
142
288
  #
143
289
  # # Update all books with 'Rails' in their title
144
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
145
- #
146
- # # Update all avatars migrated more than a week ago
147
- # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
290
+ # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
148
291
  #
149
292
  # # Update all books that match conditions, but limit it to 5 ordered by date
150
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
151
- def update_all(updates, conditions = nil, options = {})
152
- if conditions || options.present?
153
- where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
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?
296
+
297
+ stmt = Arel::UpdateManager.new(arel.engine)
298
+
299
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
300
+ stmt.table(table)
301
+ stmt.key = table[primary_key]
302
+
303
+ if with_default_scope.joins_values.any?
304
+ @klass.connection.join_to_update(stmt, arel)
154
305
  else
155
- # Apply limit and order only if they're both present
156
- if @limit_value.present? == @order_values.present?
157
- arel.update(Arel::SqlLiteral.new(@klass.send(:sanitize_sql_for_assignment, updates)))
158
- else
159
- except(:limit, :order).update_all(updates)
160
- end
306
+ stmt.take(arel.limit)
307
+ stmt.order(*arel.orders)
308
+ stmt.wheres = arel.constraints
161
309
  end
310
+
311
+ @klass.connection.update stmt, 'SQL', bind_values
162
312
  end
163
313
 
164
314
  # Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -172,29 +322,26 @@ module ActiveRecord
172
322
  # ==== Examples
173
323
  #
174
324
  # # Updates one record
175
- # Person.update(15, :user_name => 'Samuel', :group => 'expert')
325
+ # Person.update(15, user_name: 'Samuel', group: 'expert')
176
326
  #
177
327
  # # Updates multiple records
178
328
  # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
179
329
  # Person.update(people.keys, people.values)
180
330
  def update(id, attributes)
181
331
  if id.is_a?(Array)
182
- idx = -1
183
- id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
332
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
184
333
  else
185
334
  object = find(id)
186
- object.update_attributes(attributes)
335
+ object.update(attributes)
187
336
  object
188
337
  end
189
338
  end
190
339
 
191
340
  # Destroys the records matching +conditions+ by instantiating each
192
341
  # record and calling its +destroy+ method. Each object's callbacks are
193
- # executed (including <tt>:dependent</tt> association options and
194
- # +before_destroy+/+after_destroy+ Observer methods). Returns the
342
+ # executed (including <tt>:dependent</tt> association options). Returns the
195
343
  # collection of objects that were destroyed; each will be frozen, to
196
- # reflect that no changes should be made (since they can't be
197
- # persisted).
344
+ # reflect that no changes should be made (since they can't be persisted).
198
345
  #
199
346
  # Note: Instantiation, callback execution, and deletion of each
200
347
  # record can be time consuming when you're removing many records at
@@ -213,7 +360,8 @@ module ActiveRecord
213
360
  # ==== Examples
214
361
  #
215
362
  # Person.destroy_all("last_login < '2004-04-04'")
216
- # Person.destroy_all(:status => "inactive")
363
+ # Person.destroy_all(status: "inactive")
364
+ # Person.where(age: 0..18).destroy_all
217
365
  def destroy_all(conditions = nil)
218
366
  if conditions
219
367
  where(conditions).destroy_all
@@ -222,8 +370,8 @@ module ActiveRecord
222
370
  end
223
371
  end
224
372
 
225
- # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
226
- # therefore all callbacks and filters are fired off before the object is deleted. This method is
373
+ # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
374
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
227
375
  # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
228
376
  #
229
377
  # This essentially finds the object (or multiple objects) with the given id, creates a new object
@@ -249,33 +397,51 @@ module ActiveRecord
249
397
  end
250
398
  end
251
399
 
252
- # Deletes the records matching +conditions+ without instantiating the records first, and hence not
253
- # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
254
- # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
255
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
256
- # the number of rows affected.
257
- #
258
- # ==== Parameters
259
- #
260
- # * +conditions+ - Conditions are specified the same way as with +find+ method.
261
- #
262
- # ==== 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.
263
406
  #
264
407
  # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
265
408
  # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
409
+ # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
266
410
  #
267
411
  # Both calls delete the affected posts all at once with a single DELETE statement.
268
412
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
269
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
270
419
  def delete_all(conditions = nil)
271
- conditions ? where(conditions).delete_all : arel.delete.tap { reset }
420
+ raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
421
+
422
+ if conditions
423
+ where(conditions).delete_all
424
+ else
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)
435
+
436
+ reset
437
+ affected
438
+ end
272
439
  end
273
440
 
274
441
  # Deletes the row with a primary key matching the +id+ argument, using a
275
442
  # SQL +DELETE+ statement, and returns the number of rows deleted. Active
276
443
  # Record objects are not instantiated, so the object's callbacks are not
277
- # executed, including any <tt>:dependent</tt> association options or
278
- # Observer methods.
444
+ # executed, including any <tt>:dependent</tt> association options.
279
445
  #
280
446
  # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
281
447
  #
@@ -292,13 +458,25 @@ module ActiveRecord
292
458
  # # Delete multiple rows
293
459
  # Todo.delete([2,3,4])
294
460
  def delete(id_or_array)
295
- where(@klass.primary_key => id_or_array).delete_all
461
+ where(primary_key => id_or_array).delete_all
296
462
  end
297
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.
298
477
  def reload
299
478
  reset
300
- to_a # force reload
301
- self
479
+ load
302
480
  end
303
481
 
304
482
  def reset
@@ -308,86 +486,170 @@ module ActiveRecord
308
486
  self
309
487
  end
310
488
 
311
- def primary_key
312
- @primary_key ||= table[@klass.primary_key]
313
- end
314
-
489
+ # Returns sql statement for the relation.
490
+ #
491
+ # User.where(name: 'Oscar').to_sql
492
+ # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
315
493
  def to_sql
316
- @to_sql ||= arel.to_sql
494
+ @to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
317
495
  end
318
496
 
497
+ # Returns a hash of where conditions.
498
+ #
499
+ # User.where(name: 'Oscar').where_values_hash
500
+ # # => {name: "Oscar"}
319
501
  def where_values_hash
320
- Hash[@where_values.find_all { |w|
321
- w.respond_to?(:operator) && w.operator == :==
322
- }.map { |where|
323
- [where.operand1.name,
324
- where.operand2.respond_to?(:value) ?
325
- where.operand2.value : where.operand2]
326
- }]
502
+ equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
503
+ node.left.relation.name == table_name
504
+ }
505
+
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
+ }]
327
512
  end
328
513
 
329
514
  def scope_for_create
330
- @scope_for_create ||= begin
331
- @create_with_value || where_values_hash
332
- end
515
+ @scope_for_create ||= where_values_hash.merge(create_with_value)
333
516
  end
334
517
 
518
+ # Returns true if relation needs eager loading.
335
519
  def eager_loading?
336
- @should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?))
520
+ @should_eager_load ||=
521
+ eager_load_values.any? ||
522
+ includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
337
523
  end
338
524
 
525
+ # Joins that are also marked for preloading. In which case we should just eager load them.
526
+ # Note that this is a naive implementation because we could have strings and symbols which
527
+ # represent the same association, but that aren't matched by this. Also, we could have
528
+ # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
529
+ def joined_includes_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
537
+ end
538
+
539
+ # Compares two relations for equality.
339
540
  def ==(other)
340
541
  case other
341
542
  when Relation
342
543
  other.to_sql == to_sql
343
544
  when Array
344
- to_a == other.to_a
545
+ to_a == other
345
546
  end
346
547
  end
347
548
 
348
- def inspect
349
- to_a.inspect
350
- end
351
-
352
- protected
353
-
354
- def method_missing(method, *args, &block)
355
- if Array.method_defined?(method)
356
- to_a.send(method, *args, &block)
357
- elsif @klass.scopes[method]
358
- merge(@klass.send(method, *args, &block))
359
- elsif @klass.respond_to?(method)
360
- scoping { @klass.send(method, *args, &block) }
361
- elsif arel.respond_to?(method)
362
- arel.send(method, *args, &block)
363
- elsif match = DynamicFinderMatch.match(method)
364
- attributes = match.attribute_names
365
- super unless @klass.send(:all_attributes_exists?, attributes)
366
-
367
- if match.finder?
368
- find_by_attributes(match, attributes, *args)
369
- elsif match.instantiator?
370
- find_or_instantiator_by_attributes(match, attributes, *args, &block)
371
- end
549
+ def pretty_print(q)
550
+ q.pp(self.to_a)
551
+ end
552
+
553
+ def with_default_scope #:nodoc:
554
+ if default_scoped? && default_scope = klass.send(:build_default_scope)
555
+ default_scope = default_scope.merge(self)
556
+ default_scope.default_scoped = false
557
+ default_scope
372
558
  else
373
- super
559
+ self
374
560
  end
375
561
  end
376
562
 
563
+ # Returns true if relation is blank.
564
+ def blank?
565
+ to_a.blank?
566
+ end
567
+
568
+ def values
569
+ Hash[@values]
570
+ end
571
+
572
+ def inspect
573
+ entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
574
+ entries[10] = '...' if entries.size == 11
575
+
576
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
577
+ end
578
+
377
579
  private
378
580
 
581
+ def exec_queries
582
+ default_scoped = with_default_scope
583
+
584
+ if default_scoped.equal?(self)
585
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
586
+
587
+ preload = preload_values
588
+ preload += includes_values unless eager_loading?
589
+ preload.each do |associations|
590
+ ActiveRecord::Associations::Preloader.new(@records, associations).run
591
+ end
592
+
593
+ # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
594
+ # are JOINS and no explicit SELECT.
595
+ readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
596
+ @records.each { |record| record.readonly! } if readonly
597
+ else
598
+ @records = default_scoped.to_a
599
+ end
600
+
601
+ @loaded = true
602
+ @records
603
+ end
604
+
379
605
  def references_eager_loaded_tables?
606
+ joined_tables = arel.join_sources.map do |join|
607
+ if join.is_a?(Arel::Nodes::StringJoin)
608
+ tables_in_string(join.left)
609
+ else
610
+ [join.left.table_name, join.left.table_alias]
611
+ end
612
+ end
613
+
614
+ joined_tables += [table.name, table.table_alias]
615
+
380
616
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
381
- joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map{ |t| t.downcase }.uniq
382
- (tables_in_string(to_sql) - joined_tables).any?
617
+ joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
618
+ string_tables = tables_in_string(to_sql)
619
+
620
+ if (references_values - joined_tables).any?
621
+ true
622
+ elsif !ActiveRecord::Base.disable_implicit_join_references &&
623
+ (string_tables - joined_tables).any?
624
+ ActiveSupport::Deprecation.warn(
625
+ "It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \
626
+ "that are referenced in a string SQL snippet. For example: \n" \
627
+ "\n" \
628
+ " Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
629
+ "\n" \
630
+ "Currently, Active Record recognizes the table in the string, and knows to JOIN the " \
631
+ "comments table to the query, rather than loading comments in a separate query. " \
632
+ "However, doing this without writing a full-blown SQL parser is inherently flawed. " \
633
+ "Since we don't want to write an SQL parser, we are removing this functionality. " \
634
+ "From now on, you must explicitly tell Active Record when you are referencing a table " \
635
+ "from a string:\n" \
636
+ "\n" \
637
+ " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \
638
+ "\n" \
639
+ "If you don't rely on implicit join references you can disable the feature entirely " \
640
+ "by setting `config.active_record.disable_implicit_join_references = true`."
641
+ )
642
+ true
643
+ else
644
+ false
645
+ end
383
646
  end
384
647
 
385
648
  def tables_in_string(string)
386
649
  return [] if string.blank?
387
650
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
388
651
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
389
- string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
652
+ string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
390
653
  end
391
-
392
654
  end
393
655
  end