activerecord 3.2.22.5 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  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 +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  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 +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  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/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,21 +1,26 @@
1
- require 'active_support/core_ext/object/blank'
2
1
 
3
2
  module ActiveRecord
4
3
  module Batches
5
- # Yields each record that was found by the find +options+. The find is
6
- # performed by find_in_batches with a batch size of 1000 (or as
7
- # specified by the <tt>:batch_size</tt> option).
4
+ # Looping through a collection of records from the database
5
+ # (using the +all+ method, for example) is very inefficient
6
+ # since it will try to instantiate all the objects at once.
8
7
  #
9
- # Example:
8
+ # In that case, batch processing methods allow you to work
9
+ # with the records in batches, thereby greatly reducing memory consumption.
10
+ #
11
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
12
+ # specified by the +:batch_size+ option).
13
+ #
14
+ # Person.all.find_each do |person|
15
+ # person.do_awesome_stuff
16
+ # end
10
17
  #
11
18
  # Person.where("age > 21").find_each do |person|
12
19
  # person.party_all_night!
13
20
  # end
14
21
  #
15
- # Note: This method is only intended to use for batch processing of
16
- # large amounts of records that wouldn't fit in memory all at once. If
17
- # you just need to loop over less than 1000 records, it's probably
18
- # better just to use the regular find methods.
22
+ # You can also pass the +:start+ option to specify
23
+ # an offset to control the starting point.
19
24
  def find_each(options = {})
20
25
  find_in_batches(options) do |records|
21
26
  records.each { |record| yield record }
@@ -23,42 +28,40 @@ module ActiveRecord
23
28
  end
24
29
 
25
30
  # Yields each batch of records that was found by the find +options+ as
26
- # an array. The size of each batch is set by the <tt>:batch_size</tt>
31
+ # an array. The size of each batch is set by the +:batch_size+
27
32
  # option; the default is 1000.
28
33
  #
29
34
  # You can control the starting point for the batch processing by
30
- # supplying the <tt>:start</tt> option. This is especially useful if you
35
+ # supplying the +:start+ option. This is especially useful if you
31
36
  # want multiple workers dealing with the same processing queue. You can
32
37
  # make worker 1 handle all the records between id 0 and 10,000 and
33
- # worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
38
+ # worker 2 handle from 10,000 and beyond (by setting the +:start+
34
39
  # option on that worker).
35
40
  #
36
41
  # It's not possible to set the order. That is automatically set to
37
42
  # ascending on the primary key ("id ASC") to make the batch ordering
38
- # work. This also mean that this method only works with integer-based
43
+ # work. This also means that this method only works with integer-based
39
44
  # primary keys. You can't set the limit either, that's used to control
40
45
  # the batch sizes.
41
46
  #
42
- # Example:
43
- #
44
47
  # Person.where("age > 21").find_in_batches do |group|
45
48
  # sleep(50) # Make sure it doesn't get too crowded in there!
46
49
  # group.each { |person| person.party_all_night! }
47
50
  # end
51
+ #
52
+ # # Let's process the next 2000 records
53
+ # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
54
+ # group.each { |person| person.party_all_night! }
55
+ # end
48
56
  def find_in_batches(options = {})
57
+ options.assert_valid_keys(:start, :batch_size)
58
+
49
59
  relation = self
50
60
 
51
61
  unless arel.orders.blank? && arel.taken.blank?
52
62
  ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
53
63
  end
54
64
 
55
- if (finder_options = options.except(:start, :batch_size)).present?
56
- raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
57
- raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
58
-
59
- relation = apply_finder_options(finder_options)
60
- end
61
-
62
65
  start = options.delete(:start)
63
66
  batch_size = options.delete(:batch_size) || 1000
64
67
 
@@ -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.count(:age, distinct: true)
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,99 +68,133 @@ 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
85
  # drake = Family.find_by_last_name('Drake')
115
- # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
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.uniq.pluck(: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
186
172
 
187
- result.map do |attributes|
188
- klass.type_cast_attribute(last_column, klass.initialize_attributes(attributes))
173
+ result = result.map do |attributes|
174
+ values = klass.initialize_attributes(attributes).values
175
+
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
 
@@ -209,7 +213,7 @@ module ActiveRecord
209
213
  distinct = nil if column_name =~ /\s*DISTINCT\s+/i
210
214
  end
211
215
 
212
- if @group_values.any?
216
+ if group_values.any?
213
217
  execute_grouped_calculation(operation, column_name, distinct)
214
218
  else
215
219
  execute_simple_calculation(operation, column_name, distinct)
@@ -232,6 +236,8 @@ module ActiveRecord
232
236
  # Postgresql doesn't like ORDER BY when there are no GROUP BY
233
237
  relation = reorder(nil)
234
238
 
239
+ column_alias = column_name
240
+
235
241
  if operation == "count" && (relation.limit_value || relation.offset_value)
236
242
  # Shortcut when limit is zero.
237
243
  return 0 if relation.limit_value == 0
@@ -242,16 +248,24 @@ module ActiveRecord
242
248
 
243
249
  select_value = operation_over_aggregate_column(column, operation, distinct)
244
250
 
251
+ column_alias = select_value.alias
245
252
  relation.select_values = [select_value]
246
253
 
247
254
  query_builder = relation.arel
248
255
  end
249
256
 
250
- type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
257
+ result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
258
+ row = result.first
259
+ value = row && row.values.first
260
+ column = result.column_types.fetch(column_alias) do
261
+ column_for(column_name)
262
+ end
263
+
264
+ type_cast_calculated_value(value, column, operation)
251
265
  end
252
266
 
253
267
  def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
254
- group_attrs = @group_values
268
+ group_attrs = group_values
255
269
 
256
270
  if group_attrs.first.respond_to?(:to_sym)
257
271
  association = @klass.reflect_on_association(group_attrs.first.to_sym)
@@ -261,17 +275,19 @@ module ActiveRecord
261
275
  group_fields = group_attrs
262
276
  end
263
277
 
264
- group_aliases = group_fields.map { |field| column_alias_for(field) }
278
+ group_aliases = group_fields.map { |field|
279
+ column_alias_for(field)
280
+ }
265
281
  group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
266
- [aliaz, column_for(field)]
282
+ [aliaz, field]
267
283
  }
268
284
 
269
- group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
285
+ group = group_fields
270
286
 
271
287
  if operation == 'count' && column_name == :all
272
288
  aggregate_alias = 'count_all'
273
289
  else
274
- aggregate_alias = column_alias_for(operation, column_name)
290
+ aggregate_alias = column_alias_for([operation, column_name].join(' '))
275
291
  end
276
292
 
277
293
  select_values = [
@@ -280,7 +296,7 @@ module ActiveRecord
280
296
  operation,
281
297
  distinct).as(aggregate_alias)
282
298
  ]
283
- select_values += @select_values unless @having_values.empty?
299
+ select_values += select_values unless having_values.empty?
284
300
 
285
301
  select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
286
302
  if field.respond_to?(:as)
@@ -290,10 +306,11 @@ module ActiveRecord
290
306
  end
291
307
  }
292
308
 
293
- relation = except(:group).group(group)
309
+ relation = except(:group)
310
+ relation.group_values = group
294
311
  relation.select_values = select_values
295
312
 
296
- calculated_data = @klass.connection.select_all(relation)
313
+ calculated_data = @klass.connection.select_all(relation, nil, bind_values)
297
314
 
298
315
  if association
299
316
  key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -301,8 +318,11 @@ module ActiveRecord
301
318
  key_records = Hash[key_records.map { |r| [r.id, r] }]
302
319
  end
303
320
 
304
- ActiveSupport::OrderedHash[calculated_data.map do |row|
305
- key = group_columns.map { |aliaz, column|
321
+ Hash[calculated_data.map do |row|
322
+ key = group_columns.map { |aliaz, col_name|
323
+ column = calculated_data.column_types.fetch(aliaz) do
324
+ column_for(col_name)
325
+ end
306
326
  type_cast_calculated_value(row[aliaz], column)
307
327
  }
308
328
  key = key.first if key.size == 1
@@ -319,10 +339,12 @@ module ActiveRecord
319
339
  # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
320
340
  # column_alias_for("count(*)") # => "count_all"
321
341
  # 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!
342
+ def column_alias_for(keys)
343
+ if keys.respond_to? :name
344
+ keys = "#{keys.relation.name}.#{keys.name}"
345
+ end
346
+
347
+ table_name = keys.to_s.downcase
326
348
  table_name.gsub!(/\*/, 'all')
327
349
  table_name.gsub!(/\W+/, ' ')
328
350
  table_name.strip!
@@ -333,13 +355,13 @@ module ActiveRecord
333
355
 
334
356
  def column_for(field)
335
357
  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 }
358
+ @klass.columns_hash[field_name]
337
359
  end
338
360
 
339
361
  def type_cast_calculated_value(value, column, operation = nil)
340
362
  case operation
341
363
  when 'count' then value.to_i
342
- when 'sum' then type_cast_using_column(value || '0', column)
364
+ when 'sum' then type_cast_using_column(value || 0, column)
343
365
  when 'average' then value.respond_to?(:to_d) ? value.to_d : value
344
366
  else type_cast_using_column(value, column)
345
367
  end
@@ -350,9 +372,9 @@ module ActiveRecord
350
372
  end
351
373
 
352
374
  def select_for_count
353
- if @select_values.present?
354
- select = @select_values.join(", ")
355
- select if select !~ /(,|\*)/
375
+ if select_values.present?
376
+ select = select_values.join(", ")
377
+ select if select !~ /[,*]/
356
378
  end
357
379
  end
358
380