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,58 +1,24 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/object/try'
3
-
4
1
  module ActiveRecord
5
2
  module Calculations
6
- # Count operates using three different approaches.
7
- #
8
- # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
9
- # * Count using column: By passing a column name to count, it will return a count of all the
10
- # rows for the model with supplied column present.
11
- # * Count using options will find the row count matched by the options used.
12
- #
13
- # The third approach, count using options, accepts an option hash as the only parameter. The options are:
14
- #
15
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
16
- # See conditions in the intro to ActiveRecord::Base.
17
- # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id"
18
- # (rarely needed) or named associations in the same form used for the <tt>:include</tt> option, which will
19
- # perform an INNER JOIN on the associated table(s). If the value is a string, then the records
20
- # will be returned read-only since they will have attributes that do not correspond to the table's columns.
21
- # Pass <tt>:readonly => false</tt> to override.
22
- # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
23
- # The symbols named refer to already defined associations. When using named associations, count
24
- # returns the number of DISTINCT items for the model you're counting.
25
- # See eager loading under Associations.
26
- # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
27
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
28
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
29
- # want to do a join but not include the joined columns.
30
- # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
31
- # SELECT COUNT(DISTINCT posts.id) ...
32
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
33
- # alternate table name (or even the name of a database view).
34
- #
35
- # Examples for counting all:
36
- # Person.count # returns the total count of all people
3
+ # Count the records.
37
4
  #
38
- # Examples for counting by column:
39
- # Person.count(:age) # returns the total count of all people whose age is present in database
5
+ # Person.count
6
+ # # => the total count of all people
40
7
  #
41
- # Examples for count with options:
42
- # Person.count(:conditions => "age > 26")
8
+ # Person.count(:age)
9
+ # # => returns the total count of all people whose age is present in database
43
10
  #
44
- # # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
45
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
11
+ # Person.count(:all)
12
+ # # => performs a COUNT(*) (:all is an alias for '*')
46
13
  #
47
- # # finds the number of rows matching the conditions and joins.
48
- # Person.count(:conditions => "age > 26 AND job.salary > 60000",
49
- # :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
14
+ # Person.distinct.count(:age)
15
+ # # => counts the number of different age values
50
16
  #
51
- # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
52
- # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
17
+ # If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column,
18
+ # and the values are the respective amounts:
53
19
  #
54
- # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
55
- # Use Person.count instead.
20
+ # Person.group(:city).count
21
+ # # => { 'Rome' => 5, 'Paris' => 3 }
56
22
  def count(column_name = nil, options = {})
57
23
  column_name, options = nil, column_name if column_name.is_a?(Hash)
58
24
  calculate(:count, column_name, options)
@@ -91,6 +57,10 @@ module ActiveRecord
91
57
  # Person.sum('age') # => 4562
92
58
  def sum(*args)
93
59
  if block_given?
60
+ ActiveSupport::Deprecation.warn(
61
+ "Calling #sum with a block is deprecated and will be removed in Rails 4.1. " \
62
+ "If you want to perform sum calculation over the array of elements, use `to_a.sum(&block)`."
63
+ )
94
64
  self.to_a.sum(*args) {|*block_args| yield(*block_args)}
95
65
  else
96
66
  calculate(:sum, *args)
@@ -98,104 +68,143 @@ module ActiveRecord
98
68
  end
99
69
 
100
70
  # This calculates aggregate values in the given column. Methods for count, sum, average,
101
- # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
102
- # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
71
+ # minimum, and maximum have been added as shortcuts.
103
72
  #
104
73
  # There are two basic forms of output:
74
+ #
105
75
  # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
106
76
  # for AVG, and the given column's type for everything else.
107
- # * Grouped values: This returns an ordered hash of the values and groups them by the
108
- # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
109
77
  #
110
- # values = Person.maximum(:age, :group => 'last_name')
78
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
79
+ # takes either a column name, or the name of a belongs_to association.
80
+ #
81
+ # values = Person.group('last_name').maximum(:age)
111
82
  # puts values["Drake"]
112
- # => 43
83
+ # # => 43
113
84
  #
114
- # drake = Family.find_by_last_name('Drake')
115
- # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
85
+ # drake = Family.find_by(last_name: 'Drake')
86
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
116
87
  # puts values[drake]
117
- # => 43
88
+ # # => 43
118
89
  #
119
90
  # values.each do |family, max_age|
120
91
  # ...
121
92
  # end
122
93
  #
123
- # Options:
124
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
125
- # See conditions in the intro to ActiveRecord::Base.
126
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
127
- # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
128
- # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
129
- # (Rarely needed).
130
- # The records will be returned read-only since they will have attributes that do not correspond to the
131
- # table's columns.
132
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
133
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
134
- # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
135
- # want to do a join, but not include the joined columns.
136
- # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
137
- # SELECT COUNT(DISTINCT posts.id) ...
138
- #
139
- # Examples:
140
94
  # Person.calculate(:count, :all) # The same as Person.count
141
95
  # Person.average(:age) # SELECT AVG(age) FROM people...
142
- # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
143
- # # everyone with a last name other than 'Drake'
144
96
  #
145
97
  # # Selects the minimum age for any family without any minors
146
- # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
98
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
147
99
  #
148
100
  # Person.sum("2 * age")
149
101
  def calculate(operation, column_name, options = {})
150
- if options.except(:distinct).present?
151
- apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
152
- else
153
- relation = with_default_scope
102
+ relation = with_default_scope
154
103
 
155
- if relation.equal?(self)
156
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
157
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
158
- else
159
- perform_calculation(operation, column_name, options)
160
- end
104
+ if relation.equal?(self)
105
+ if has_include?(column_name)
106
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
161
107
  else
162
- relation.calculate(operation, column_name, options)
108
+ perform_calculation(operation, column_name, options)
163
109
  end
110
+ else
111
+ relation.calculate(operation, column_name, options)
164
112
  end
165
113
  rescue ThrowResult
166
114
  0
167
115
  end
168
116
 
169
- # This method is designed to perform select by a single column as direct SQL query
170
- # Returns <tt>Array</tt> with values of the specified column name
171
- # The values has same data type as column.
117
+ # Use <tt>pluck</tt> as a shortcut to select one or more attributes without
118
+ # loading a bunch of records just to grab the attributes you want.
119
+ #
120
+ # Person.pluck(:name)
121
+ #
122
+ # instead of
123
+ #
124
+ # Person.all.map(&:name)
125
+ #
126
+ # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
127
+ # the plucked column names, if they can be deduced. Plucking an SQL fragment
128
+ # returns String values by default.
172
129
  #
173
- # Examples:
130
+ # Person.pluck(:id)
131
+ # # SELECT people.id FROM people
132
+ # # => [1, 2, 3]
174
133
  #
175
- # Person.pluck(:id) # SELECT people.id FROM people
176
- # Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
177
- # Person.where(:confirmed => true).limit(5).pluck(:id)
134
+ # Person.pluck(:id, :name)
135
+ # # SELECT people.id, people.name FROM people
136
+ # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
178
137
  #
179
- def pluck(column_name)
180
- if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
181
- column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
138
+ # Person.pluck('DISTINCT role')
139
+ # # SELECT DISTINCT role FROM people
140
+ # # => ['admin', 'member', 'guest']
141
+ #
142
+ # Person.where(age: 21).limit(5).pluck(:id)
143
+ # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
144
+ # # => [2, 3]
145
+ #
146
+ # Person.pluck('DATEDIFF(updated_at, created_at)')
147
+ # # SELECT DATEDIFF(updated_at, created_at) FROM people
148
+ # # => ['0', '27761', '173']
149
+ #
150
+ def pluck(*column_names)
151
+ column_names.map! do |column_name|
152
+ if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
153
+ "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
154
+ else
155
+ column_name
156
+ end
182
157
  end
183
158
 
184
- result = klass.connection.exec_query(select(column_name).to_sql)
185
- last_column = result.columns.last
159
+ if has_include?(column_names.first)
160
+ construct_relation_for_association_calculations.pluck(*column_names)
161
+ else
162
+ relation = spawn
163
+ relation.select_values = column_names
164
+ result = klass.connection.select_all(relation.arel, nil, bind_values)
165
+ columns = result.columns.map do |key|
166
+ klass.column_types.fetch(key) {
167
+ result.column_types.fetch(key) {
168
+ Class.new { def type_cast(v); v; end }.new
169
+ }
170
+ }
171
+ end
172
+
173
+ result = result.map do |attributes|
174
+ values = klass.initialize_attributes(attributes).values
186
175
 
187
- result.map do |attributes|
188
- klass.type_cast_attribute(last_column, klass.initialize_attributes(attributes))
176
+ columns.zip(values).map do |column, value|
177
+ column.type_cast(value)
178
+ end
179
+ end
180
+ columns.one? ? result.map!(&:first) : result
189
181
  end
190
182
  end
191
183
 
184
+ # Pluck all the ID's for the relation using the table's primary key
185
+ #
186
+ # Person.ids # SELECT people.id FROM people
187
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
188
+ def ids
189
+ pluck primary_key
190
+ end
191
+
192
192
  private
193
193
 
194
+ def has_include?(column_name)
195
+ eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
196
+ end
197
+
194
198
  def perform_calculation(operation, column_name, options = {})
195
199
  operation = operation.to_s.downcase
196
200
 
197
- # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count)
198
- distinct = options[:distinct] || self.uniq_value
201
+ # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
202
+ distinct = self.distinct_value
203
+ if options.has_key?(:distinct)
204
+ ActiveSupport::Deprecation.warn "The :distinct option for `Relation#count` is deprecated. " \
205
+ "Please use `Relation#distinct` instead. (eg. `relation.distinct.count`)"
206
+ distinct = options[:distinct]
207
+ end
199
208
 
200
209
  if operation == "count"
201
210
  column_name ||= (select_for_count || :all)
@@ -209,7 +218,7 @@ module ActiveRecord
209
218
  distinct = nil if column_name =~ /\s*DISTINCT\s+/i
210
219
  end
211
220
 
212
- if @group_values.any?
221
+ if group_values.any?
213
222
  execute_grouped_calculation(operation, column_name, distinct)
214
223
  else
215
224
  execute_simple_calculation(operation, column_name, distinct)
@@ -232,6 +241,8 @@ module ActiveRecord
232
241
  # Postgresql doesn't like ORDER BY when there are no GROUP BY
233
242
  relation = reorder(nil)
234
243
 
244
+ column_alias = column_name
245
+
235
246
  if operation == "count" && (relation.limit_value || relation.offset_value)
236
247
  # Shortcut when limit is zero.
237
248
  return 0 if relation.limit_value == 0
@@ -242,16 +253,24 @@ module ActiveRecord
242
253
 
243
254
  select_value = operation_over_aggregate_column(column, operation, distinct)
244
255
 
256
+ column_alias = select_value.alias
245
257
  relation.select_values = [select_value]
246
258
 
247
259
  query_builder = relation.arel
248
260
  end
249
261
 
250
- type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
262
+ result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
263
+ row = result.first
264
+ value = row && row.values.first
265
+ column = result.column_types.fetch(column_alias) do
266
+ column_for(column_name)
267
+ end
268
+
269
+ type_cast_calculated_value(value, column, operation)
251
270
  end
252
271
 
253
272
  def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
254
- group_attrs = @group_values
273
+ group_attrs = group_values
255
274
 
256
275
  if group_attrs.first.respond_to?(:to_sym)
257
276
  association = @klass.reflect_on_association(group_attrs.first.to_sym)
@@ -261,17 +280,19 @@ module ActiveRecord
261
280
  group_fields = group_attrs
262
281
  end
263
282
 
264
- group_aliases = group_fields.map { |field| column_alias_for(field) }
283
+ group_aliases = group_fields.map { |field|
284
+ column_alias_for(field)
285
+ }
265
286
  group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
266
- [aliaz, column_for(field)]
287
+ [aliaz, field]
267
288
  }
268
289
 
269
- group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
290
+ group = group_fields
270
291
 
271
292
  if operation == 'count' && column_name == :all
272
293
  aggregate_alias = 'count_all'
273
294
  else
274
- aggregate_alias = column_alias_for(operation, column_name)
295
+ aggregate_alias = column_alias_for([operation, column_name].join(' '))
275
296
  end
276
297
 
277
298
  select_values = [
@@ -280,7 +301,7 @@ module ActiveRecord
280
301
  operation,
281
302
  distinct).as(aggregate_alias)
282
303
  ]
283
- select_values += @select_values unless @having_values.empty?
304
+ select_values += select_values unless having_values.empty?
284
305
 
285
306
  select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
286
307
  if field.respond_to?(:as)
@@ -290,10 +311,11 @@ module ActiveRecord
290
311
  end
291
312
  }
292
313
 
293
- relation = except(:group).group(group)
314
+ relation = except(:group)
315
+ relation.group_values = group
294
316
  relation.select_values = select_values
295
317
 
296
- calculated_data = @klass.connection.select_all(relation)
318
+ calculated_data = @klass.connection.select_all(relation, nil, bind_values)
297
319
 
298
320
  if association
299
321
  key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -301,13 +323,18 @@ module ActiveRecord
301
323
  key_records = Hash[key_records.map { |r| [r.id, r] }]
302
324
  end
303
325
 
304
- ActiveSupport::OrderedHash[calculated_data.map do |row|
305
- key = group_columns.map { |aliaz, column|
326
+ Hash[calculated_data.map do |row|
327
+ key = group_columns.map { |aliaz, col_name|
328
+ column = calculated_data.column_types.fetch(aliaz) do
329
+ column_for(col_name)
330
+ end
306
331
  type_cast_calculated_value(row[aliaz], column)
307
332
  }
308
333
  key = key.first if key.size == 1
309
334
  key = key_records[key] if associated
310
- [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
335
+
336
+ column_type = calculated_data.column_types.fetch(aggregate_alias) { column_for(column_name) }
337
+ [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
311
338
  end]
312
339
  end
313
340
 
@@ -319,10 +346,12 @@ module ActiveRecord
319
346
  # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
320
347
  # column_alias_for("count(*)") # => "count_all"
321
348
  # column_alias_for("count", "id") # => "count_id"
322
- def column_alias_for(*keys)
323
- keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k}
324
- table_name = keys.join(' ')
325
- table_name.downcase!
349
+ def column_alias_for(keys)
350
+ if keys.respond_to? :name
351
+ keys = "#{keys.relation.name}.#{keys.name}"
352
+ end
353
+
354
+ table_name = keys.to_s.downcase
326
355
  table_name.gsub!(/\*/, 'all')
327
356
  table_name.gsub!(/\W+/, ' ')
328
357
  table_name.strip!
@@ -333,13 +362,13 @@ module ActiveRecord
333
362
 
334
363
  def column_for(field)
335
364
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
336
- @klass.columns.detect { |c| c.name.to_s == field_name }
365
+ @klass.columns_hash[field_name]
337
366
  end
338
367
 
339
368
  def type_cast_calculated_value(value, column, operation = nil)
340
369
  case operation
341
370
  when 'count' then value.to_i
342
- when 'sum' then type_cast_using_column(value || '0', column)
371
+ when 'sum' then type_cast_using_column(value || 0, column)
343
372
  when 'average' then value.respond_to?(:to_d) ? value.to_d : value
344
373
  else type_cast_using_column(value, column)
345
374
  end
@@ -349,10 +378,11 @@ module ActiveRecord
349
378
  column ? column.type_cast(value) : value
350
379
  end
351
380
 
381
+ # TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
352
382
  def select_for_count
353
- if @select_values.present?
354
- select = @select_values.join(", ")
355
- select if select !~ /(,|\*)/
383
+ if select_values.present?
384
+ select = select_values.join(", ")
385
+ select if select !~ /[,*]/
356
386
  end
357
387
  end
358
388
 
@@ -1,25 +1,104 @@
1
- require 'active_support/core_ext/module/delegation'
1
+ require 'thread'
2
+ require 'thread_safe'
2
3
 
3
4
  module ActiveRecord
4
- module Delegation
5
- # Set up common delegations for performance (avoids method_missing)
5
+ module Delegation # :nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ # This module creates compiled delegation methods dynamically at runtime, which makes
9
+ # subsequent calls to that method faster by avoiding method_missing. The delegations
10
+ # may vary depending on the klass of a relation, so we create a subclass of Relation
11
+ # for each different klass, and the delegations are compiled into that subclass only.
12
+
6
13
  delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
7
14
  delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
8
- :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
15
+ :connection, :columns_hash, :to => :klass
16
+
17
+ module ClassSpecificRelation # :nodoc:
18
+ extend ActiveSupport::Concern
19
+
20
+ included do
21
+ @delegation_mutex = Mutex.new
22
+ end
23
+
24
+ module ClassMethods # :nodoc:
25
+ def name
26
+ superclass.name
27
+ end
9
28
 
10
- def self.delegate_to_scoped_klass(method)
11
- if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
12
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
13
- def #{method}(*args, &block)
14
- scoping { @klass.#{method}(*args, &block) }
29
+ def delegate_to_scoped_klass(method)
30
+ @delegation_mutex.synchronize do
31
+ return if method_defined?(method)
32
+
33
+ if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
34
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
35
+ def #{method}(*args, &block)
36
+ scoping { @klass.#{method}(*args, &block) }
37
+ end
38
+ RUBY
39
+ else
40
+ define_method method do |*args, &block|
41
+ scoping { @klass.send(method, *args, &block) }
42
+ end
43
+ end
15
44
  end
16
- RUBY
17
- else
18
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
19
- def #{method}(*args, &block)
20
- scoping { @klass.send(#{method.inspect}, *args, &block) }
45
+ end
46
+
47
+ def delegate(method, opts = {})
48
+ @delegation_mutex.synchronize do
49
+ return if method_defined?(method)
50
+ super
51
+ end
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def method_missing(method, *args, &block)
58
+ if @klass.respond_to?(method)
59
+ self.class.delegate_to_scoped_klass(method)
60
+ scoping { @klass.send(method, *args, &block) }
61
+ elsif Array.method_defined?(method)
62
+ self.class.delegate method, :to => :to_a
63
+ to_a.send(method, *args, &block)
64
+ elsif arel.respond_to?(method)
65
+ self.class.delegate method, :to => :arel
66
+ arel.send(method, *args, &block)
67
+ else
68
+ super
69
+ end
70
+ end
71
+ end
72
+
73
+ module ClassMethods # :nodoc:
74
+ @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2)
75
+
76
+ def new(klass, *args)
77
+ relation = relation_class_for(klass).allocate
78
+ relation.__send__(:initialize, klass, *args)
79
+ relation
80
+ end
81
+
82
+ # This doesn't have to be thread-safe. relation_class_for guarantees that this will only be
83
+ # called exactly once for a given const name.
84
+ def const_missing(name)
85
+ const_set(name, Class.new(self) { include ClassSpecificRelation })
86
+ end
87
+
88
+ private
89
+ # Cache the constants in @@subclasses because looking them up via const_get
90
+ # make instantiation significantly slower.
91
+ def relation_class_for(klass)
92
+ if klass && (klass_name = klass.name)
93
+ my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new }
94
+ # This hash is keyed by klass.name to avoid memory leaks in development mode
95
+ my_cache.compute_if_absent(klass_name) do
96
+ # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name
97
+ const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false)
21
98
  end
22
- RUBY
99
+ else
100
+ ActiveRecord::Relation
101
+ end
23
102
  end
24
103
  end
25
104
 
@@ -33,13 +112,10 @@ module ActiveRecord
33
112
 
34
113
  def method_missing(method, *args, &block)
35
114
  if @klass.respond_to?(method)
36
- ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
37
115
  scoping { @klass.send(method, *args, &block) }
38
116
  elsif Array.method_defined?(method)
39
- ::ActiveRecord::Delegation.delegate method, :to => :to_a
40
117
  to_a.send(method, *args, &block)
41
118
  elsif arel.respond_to?(method)
42
- ::ActiveRecord::Delegation.delegate method, :to => :arel
43
119
  arel.send(method, *args, &block)
44
120
  else
45
121
  super