activerecord 1.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 (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,93 @@
1
+
2
+ module ActiveRecord
3
+ module Batches
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.
7
+ #
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
17
+ #
18
+ # Person.where("age > 21").find_each do |person|
19
+ # person.party_all_night!
20
+ # end
21
+ #
22
+ # You can also pass the +:start+ option to specify
23
+ # an offset to control the starting point.
24
+ def find_each(options = {})
25
+ find_in_batches(options) do |records|
26
+ records.each { |record| yield record }
27
+ end
28
+ end
29
+
30
+ # Yields each batch of records that was found by the find +options+ as
31
+ # an array. The size of each batch is set by the +:batch_size+
32
+ # option; the default is 1000.
33
+ #
34
+ # You can control the starting point for the batch processing by
35
+ # supplying the +:start+ option. This is especially useful if you
36
+ # want multiple workers dealing with the same processing queue. You can
37
+ # make worker 1 handle all the records between id 0 and 10,000 and
38
+ # worker 2 handle from 10,000 and beyond (by setting the +:start+
39
+ # option on that worker).
40
+ #
41
+ # It's not possible to set the order. That is automatically set to
42
+ # ascending on the primary key ("id ASC") to make the batch ordering
43
+ # work. This also means that this method only works with integer-based
44
+ # primary keys. You can't set the limit either, that's used to control
45
+ # the batch sizes.
46
+ #
47
+ # Person.where("age > 21").find_in_batches do |group|
48
+ # sleep(50) # Make sure it doesn't get too crowded in there!
49
+ # group.each { |person| person.party_all_night! }
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
56
+ def find_in_batches(options = {})
57
+ options.assert_valid_keys(:start, :batch_size)
58
+
59
+ relation = self
60
+
61
+ unless arel.orders.blank? && arel.taken.blank?
62
+ ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
63
+ end
64
+
65
+ start = options.delete(:start)
66
+ batch_size = options.delete(:batch_size) || 1000
67
+
68
+ relation = relation.reorder(batch_order).limit(batch_size)
69
+ records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
70
+
71
+ while records.any?
72
+ records_size = records.size
73
+ primary_key_offset = records.last.id
74
+
75
+ yield records
76
+
77
+ break if records_size < batch_size
78
+
79
+ if primary_key_offset
80
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
81
+ else
82
+ raise "Primary key not included in the custom select clause"
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def batch_order
90
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,399 @@
1
+ module ActiveRecord
2
+ module Calculations
3
+ # Count the records.
4
+ #
5
+ # Person.count
6
+ # # => the total count of all people
7
+ #
8
+ # Person.count(:age)
9
+ # # => returns the total count of all people whose age is present in database
10
+ #
11
+ # Person.count(:all)
12
+ # # => performs a COUNT(*) (:all is an alias for '*')
13
+ #
14
+ # Person.distinct.count(:age)
15
+ # # => counts the number of different age values
16
+ #
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:
19
+ #
20
+ # Person.group(:city).count
21
+ # # => { 'Rome' => 5, 'Paris' => 3 }
22
+ def count(column_name = nil, options = {})
23
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
24
+ calculate(:count, column_name, options)
25
+ end
26
+
27
+ # Calculates the average value on a given column. Returns +nil+ if there's
28
+ # no row. See +calculate+ for examples with options.
29
+ #
30
+ # Person.average('age') # => 35.8
31
+ def average(column_name, options = {})
32
+ calculate(:average, column_name, options)
33
+ end
34
+
35
+ # Calculates the minimum value on a given column. The value is returned
36
+ # with the same data type of the column, or +nil+ if there's no row. See
37
+ # +calculate+ for examples with options.
38
+ #
39
+ # Person.minimum('age') # => 7
40
+ def minimum(column_name, options = {})
41
+ calculate(:minimum, column_name, options)
42
+ end
43
+
44
+ # Calculates the maximum value on a given column. The value is returned
45
+ # with the same data type of the column, or +nil+ if there's no row. See
46
+ # +calculate+ for examples with options.
47
+ #
48
+ # Person.maximum('age') # => 93
49
+ def maximum(column_name, options = {})
50
+ calculate(:maximum, column_name, options)
51
+ end
52
+
53
+ # Calculates the sum of values on a given column. The value is returned
54
+ # with the same data type of the column, 0 if there's no row. See
55
+ # +calculate+ for examples with options.
56
+ #
57
+ # Person.sum('age') # => 4562
58
+ def sum(*args)
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
+ )
64
+ self.to_a.sum(*args) {|*block_args| yield(*block_args)}
65
+ else
66
+ calculate(:sum, *args)
67
+ end
68
+ end
69
+
70
+ # This calculates aggregate values in the given column. Methods for count, sum, average,
71
+ # minimum, and maximum have been added as shortcuts.
72
+ #
73
+ # There are two basic forms of output:
74
+ #
75
+ # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
76
+ # for AVG, and the given column's type for everything else.
77
+ #
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)
82
+ # puts values["Drake"]
83
+ # # => 43
84
+ #
85
+ # drake = Family.find_by(last_name: 'Drake')
86
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
87
+ # puts values[drake]
88
+ # # => 43
89
+ #
90
+ # values.each do |family, max_age|
91
+ # ...
92
+ # end
93
+ #
94
+ # Person.calculate(:count, :all) # The same as Person.count
95
+ # Person.average(:age) # SELECT AVG(age) FROM people...
96
+ #
97
+ # # Selects the minimum age for any family without any minors
98
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
99
+ #
100
+ # Person.sum("2 * age")
101
+ def calculate(operation, column_name, options = {})
102
+ relation = with_default_scope
103
+
104
+ if relation.equal?(self)
105
+ if has_include?(column_name)
106
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
107
+ else
108
+ perform_calculation(operation, column_name, options)
109
+ end
110
+ else
111
+ relation.calculate(operation, column_name, options)
112
+ end
113
+ rescue ThrowResult
114
+ 0
115
+ end
116
+
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.
129
+ #
130
+ # Person.pluck(:id)
131
+ # # SELECT people.id FROM people
132
+ # # => [1, 2, 3]
133
+ #
134
+ # Person.pluck(:id, :name)
135
+ # # SELECT people.id, people.name FROM people
136
+ # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
137
+ #
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
157
+ end
158
+
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
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
181
+ end
182
+ end
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
+ private
193
+
194
+ def has_include?(column_name)
195
+ eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
196
+ end
197
+
198
+ def perform_calculation(operation, column_name, options = {})
199
+ operation = operation.to_s.downcase
200
+
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
208
+
209
+ if operation == "count"
210
+ column_name ||= (select_for_count || :all)
211
+
212
+ unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
213
+ distinct = true
214
+ end
215
+
216
+ column_name = primary_key if column_name == :all && distinct
217
+
218
+ distinct = nil if column_name =~ /\s*DISTINCT\s+/i
219
+ end
220
+
221
+ if group_values.any?
222
+ execute_grouped_calculation(operation, column_name, distinct)
223
+ else
224
+ execute_simple_calculation(operation, column_name, distinct)
225
+ end
226
+ end
227
+
228
+ def aggregate_column(column_name)
229
+ if @klass.column_names.include?(column_name.to_s)
230
+ Arel::Attribute.new(@klass.unscoped.table, column_name)
231
+ else
232
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
233
+ end
234
+ end
235
+
236
+ def operation_over_aggregate_column(column, operation, distinct)
237
+ operation == 'count' ? column.count(distinct) : column.send(operation)
238
+ end
239
+
240
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
241
+ # Postgresql doesn't like ORDER BY when there are no GROUP BY
242
+ relation = reorder(nil)
243
+
244
+ column_alias = column_name
245
+
246
+ if operation == "count" && (relation.limit_value || relation.offset_value)
247
+ # Shortcut when limit is zero.
248
+ return 0 if relation.limit_value == 0
249
+
250
+ query_builder = build_count_subquery(relation, column_name, distinct)
251
+ else
252
+ column = aggregate_column(column_name)
253
+
254
+ select_value = operation_over_aggregate_column(column, operation, distinct)
255
+
256
+ column_alias = select_value.alias
257
+ relation.select_values = [select_value]
258
+
259
+ query_builder = relation.arel
260
+ end
261
+
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)
270
+ end
271
+
272
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
273
+ group_attrs = group_values
274
+
275
+ if group_attrs.first.respond_to?(:to_sym)
276
+ association = @klass.reflect_on_association(group_attrs.first.to_sym)
277
+ associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
278
+ group_fields = Array(associated ? association.foreign_key : group_attrs)
279
+ else
280
+ group_fields = group_attrs
281
+ end
282
+
283
+ group_aliases = group_fields.map { |field|
284
+ column_alias_for(field)
285
+ }
286
+ group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
287
+ [aliaz, field]
288
+ }
289
+
290
+ group = group_fields
291
+
292
+ if operation == 'count' && column_name == :all
293
+ aggregate_alias = 'count_all'
294
+ else
295
+ aggregate_alias = column_alias_for([operation, column_name].join(' '))
296
+ end
297
+
298
+ select_values = [
299
+ operation_over_aggregate_column(
300
+ aggregate_column(column_name),
301
+ operation,
302
+ distinct).as(aggregate_alias)
303
+ ]
304
+ select_values += select_values unless having_values.empty?
305
+
306
+ select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
307
+ if field.respond_to?(:as)
308
+ field.as(aliaz)
309
+ else
310
+ "#{field} AS #{aliaz}"
311
+ end
312
+ }
313
+
314
+ relation = except(:group)
315
+ relation.group_values = group
316
+ relation.select_values = select_values
317
+
318
+ calculated_data = @klass.connection.select_all(relation, nil, bind_values)
319
+
320
+ if association
321
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
322
+ key_records = association.klass.base_class.find(key_ids)
323
+ key_records = Hash[key_records.map { |r| [r.id, r] }]
324
+ end
325
+
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
331
+ type_cast_calculated_value(row[aliaz], column)
332
+ }
333
+ key = key.first if key.size == 1
334
+ key = key_records[key] if associated
335
+ [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
336
+ end]
337
+ end
338
+
339
+ # Converts the given keys to the value that the database adapter returns as
340
+ # a usable column name:
341
+ #
342
+ # column_alias_for("users.id") # => "users_id"
343
+ # column_alias_for("sum(id)") # => "sum_id"
344
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
345
+ # column_alias_for("count(*)") # => "count_all"
346
+ # column_alias_for("count", "id") # => "count_id"
347
+ def column_alias_for(keys)
348
+ if keys.respond_to? :name
349
+ keys = "#{keys.relation.name}.#{keys.name}"
350
+ end
351
+
352
+ table_name = keys.to_s.downcase
353
+ table_name.gsub!(/\*/, 'all')
354
+ table_name.gsub!(/\W+/, ' ')
355
+ table_name.strip!
356
+ table_name.gsub!(/ +/, '_')
357
+
358
+ @klass.connection.table_alias_for(table_name)
359
+ end
360
+
361
+ def column_for(field)
362
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
363
+ @klass.columns_hash[field_name]
364
+ end
365
+
366
+ def type_cast_calculated_value(value, column, operation = nil)
367
+ case operation
368
+ when 'count' then value.to_i
369
+ when 'sum' then type_cast_using_column(value || 0, column)
370
+ when 'average' then value.respond_to?(:to_d) ? value.to_d : value
371
+ else type_cast_using_column(value, column)
372
+ end
373
+ end
374
+
375
+ def type_cast_using_column(value, column)
376
+ column ? column.type_cast(value) : value
377
+ end
378
+
379
+ def select_for_count
380
+ if select_values.present?
381
+ select = select_values.join(", ")
382
+ select if select !~ /[,*]/
383
+ end
384
+ end
385
+
386
+ def build_count_subquery(relation, column_name, distinct)
387
+ column_alias = Arel.sql('count_column')
388
+ subquery_alias = Arel.sql('subquery_for_count')
389
+
390
+ aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
391
+ relation.select_values = [aliased_column]
392
+ subquery = relation.arel.as(subquery_alias)
393
+
394
+ sm = Arel::SelectManager.new relation.engine
395
+ select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
396
+ sm.project(select_value).from(subquery)
397
+ end
398
+ end
399
+ end
@@ -0,0 +1,125 @@
1
+ require 'thread'
2
+ require 'thread_safe'
3
+
4
+ module ActiveRecord
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
+
13
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
14
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
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
28
+
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
44
+ end
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)
98
+ end
99
+ else
100
+ ActiveRecord::Relation
101
+ end
102
+ end
103
+ end
104
+
105
+ def respond_to?(method, include_private = false)
106
+ super || Array.method_defined?(method) ||
107
+ @klass.respond_to?(method, include_private) ||
108
+ arel.respond_to?(method, include_private)
109
+ end
110
+
111
+ protected
112
+
113
+ def method_missing(method, *args, &block)
114
+ if @klass.respond_to?(method)
115
+ scoping { @klass.send(method, *args, &block) }
116
+ elsif Array.method_defined?(method)
117
+ to_a.send(method, *args, &block)
118
+ elsif arel.respond_to?(method)
119
+ arel.send(method, *args, &block)
120
+ else
121
+ super
122
+ end
123
+ end
124
+ end
125
+ end