activerecord 4.2.0 → 5.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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1537 -789
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record/aggregations.rb +37 -23
  8. data/lib/active_record/association_relation.rb +16 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +23 -9
  11. data/lib/active_record/associations/association_scope.rb +74 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +26 -29
  13. data/lib/active_record/associations/builder/association.rb +28 -34
  14. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  15. data/lib/active_record/associations/builder/collection_association.rb +12 -20
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +22 -15
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +11 -6
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  20. data/lib/active_record/associations/collection_association.rb +61 -33
  21. data/lib/active_record/associations/collection_proxy.rb +81 -35
  22. data/lib/active_record/associations/foreign_association.rb +11 -0
  23. data/lib/active_record/associations/has_many_association.rb +21 -57
  24. data/lib/active_record/associations/has_many_through_association.rb +15 -45
  25. data/lib/active_record/associations/has_one_association.rb +13 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
  27. data/lib/active_record/associations/join_dependency.rb +37 -21
  28. data/lib/active_record/associations/preloader/association.rb +51 -53
  29. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  30. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  31. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  32. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  33. data/lib/active_record/associations/preloader.rb +18 -8
  34. data/lib/active_record/associations/singular_association.rb +8 -8
  35. data/lib/active_record/associations/through_association.rb +22 -9
  36. data/lib/active_record/associations.rb +321 -212
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +79 -15
  39. data/lib/active_record/attribute_assignment.rb +20 -141
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +6 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +51 -81
  43. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  44. data/lib/active_record/attribute_methods/query.rb +2 -2
  45. data/lib/active_record/attribute_methods/read.rb +31 -59
  46. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -14
  48. data/lib/active_record/attribute_methods/write.rb +14 -38
  49. data/lib/active_record/attribute_methods.rb +70 -45
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +37 -15
  52. data/lib/active_record/attribute_set.rb +34 -3
  53. data/lib/active_record/attributes.rb +199 -73
  54. data/lib/active_record/autosave_association.rb +73 -25
  55. data/lib/active_record/base.rb +35 -27
  56. data/lib/active_record/callbacks.rb +39 -43
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +20 -8
  59. data/lib/active_record/collection_cache_key.rb +40 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +457 -181
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -59
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -4
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +246 -185
  68. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +438 -136
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +53 -40
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -66
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +429 -335
  73. data/lib/active_record/connection_adapters/column.rb +28 -43
  74. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  75. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  78. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  79. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  83. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -177
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +11 -73
  87. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -56
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -13
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  101. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  102. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  103. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  106. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  107. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  108. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  109. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +248 -154
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +258 -170
  113. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  114. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  115. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  116. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +150 -209
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +38 -15
  121. data/lib/active_record/core.rb +109 -114
  122. data/lib/active_record/counter_cache.rb +14 -25
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +115 -79
  125. data/lib/active_record/errors.rb +88 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +2 -2
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +84 -46
  130. data/lib/active_record/gem_version.rb +2 -2
  131. data/lib/active_record/inheritance.rb +32 -40
  132. data/lib/active_record/integration.rb +4 -4
  133. data/lib/active_record/internal_metadata.rb +56 -0
  134. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +27 -25
  137. data/lib/active_record/locking/pessimistic.rb +1 -1
  138. data/lib/active_record/log_subscriber.rb +43 -21
  139. data/lib/active_record/migration/command_recorder.rb +59 -18
  140. data/lib/active_record/migration/compatibility.rb +126 -0
  141. data/lib/active_record/migration.rb +372 -114
  142. data/lib/active_record/model_schema.rb +128 -38
  143. data/lib/active_record/nested_attributes.rb +71 -32
  144. data/lib/active_record/no_touching.rb +1 -1
  145. data/lib/active_record/null_relation.rb +16 -8
  146. data/lib/active_record/persistence.rb +124 -80
  147. data/lib/active_record/query_cache.rb +15 -18
  148. data/lib/active_record/querying.rb +10 -9
  149. data/lib/active_record/railtie.rb +28 -19
  150. data/lib/active_record/railties/controller_runtime.rb +1 -1
  151. data/lib/active_record/railties/databases.rake +67 -51
  152. data/lib/active_record/readonly_attributes.rb +1 -1
  153. data/lib/active_record/reflection.rb +318 -139
  154. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  155. data/lib/active_record/relation/batches.rb +139 -34
  156. data/lib/active_record/relation/calculations.rb +80 -102
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +167 -97
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +38 -41
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -16
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  167. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  168. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  169. data/lib/active_record/relation/predicate_builder.rb +124 -82
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +323 -257
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +11 -10
  174. data/lib/active_record/relation/where_clause.rb +174 -0
  175. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  176. data/lib/active_record/relation.rb +176 -115
  177. data/lib/active_record/result.rb +4 -3
  178. data/lib/active_record/runtime_registry.rb +1 -1
  179. data/lib/active_record/sanitization.rb +95 -66
  180. data/lib/active_record/schema.rb +26 -22
  181. data/lib/active_record/schema_dumper.rb +62 -38
  182. data/lib/active_record/schema_migration.rb +11 -17
  183. data/lib/active_record/scoping/default.rb +24 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/scoping.rb +32 -15
  186. data/lib/active_record/secure_token.rb +38 -0
  187. data/lib/active_record/serialization.rb +2 -4
  188. data/lib/active_record/statement_cache.rb +16 -14
  189. data/lib/active_record/store.rb +8 -3
  190. data/lib/active_record/suppressor.rb +58 -0
  191. data/lib/active_record/table_metadata.rb +68 -0
  192. data/lib/active_record/tasks/database_tasks.rb +59 -42
  193. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -26
  194. data/lib/active_record/tasks/postgresql_database_tasks.rb +29 -9
  195. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  196. data/lib/active_record/timestamp.rb +20 -9
  197. data/lib/active_record/touch_later.rb +58 -0
  198. data/lib/active_record/transactions.rb +159 -67
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -41
  201. data/lib/active_record/type/date_time.rb +2 -38
  202. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  203. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  204. data/lib/active_record/type/internal/timezone.rb +15 -0
  205. data/lib/active_record/type/serialized.rb +21 -14
  206. data/lib/active_record/type/time.rb +10 -16
  207. data/lib/active_record/type/type_map.rb +4 -4
  208. data/lib/active_record/type.rb +66 -17
  209. data/lib/active_record/type_caster/connection.rb +29 -0
  210. data/lib/active_record/type_caster/map.rb +19 -0
  211. data/lib/active_record/type_caster.rb +7 -0
  212. data/lib/active_record/validations/absence.rb +23 -0
  213. data/lib/active_record/validations/associated.rb +10 -3
  214. data/lib/active_record/validations/length.rb +24 -0
  215. data/lib/active_record/validations/presence.rb +11 -12
  216. data/lib/active_record/validations/uniqueness.rb +29 -18
  217. data/lib/active_record/validations.rb +33 -32
  218. data/lib/active_record.rb +9 -2
  219. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  220. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -6
  221. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -7
  222. data/lib/rails/generators/active_record/migration.rb +7 -0
  223. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  224. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  225. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  226. metadata +60 -34
  227. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  228. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  229. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  231. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  232. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  233. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  234. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  235. data/lib/active_record/type/big_integer.rb +0 -13
  236. data/lib/active_record/type/binary.rb +0 -50
  237. data/lib/active_record/type/boolean.rb +0 -30
  238. data/lib/active_record/type/decimal.rb +0 -40
  239. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  240. data/lib/active_record/type/decorator.rb +0 -14
  241. data/lib/active_record/type/float.rb +0 -19
  242. data/lib/active_record/type/integer.rb +0 -55
  243. data/lib/active_record/type/mutable.rb +0 -16
  244. data/lib/active_record/type/numeric.rb +0 -36
  245. data/lib/active_record/type/string.rb +0 -36
  246. data/lib/active_record/type/text.rb +0 -11
  247. data/lib/active_record/type/time_value.rb +0 -38
  248. data/lib/active_record/type/unsigned_integer.rb +0 -15
  249. data/lib/active_record/type/value.rb +0 -101
@@ -1,4 +1,3 @@
1
- require 'active_support/deprecation'
2
1
  require 'active_support/core_ext/string/filters'
3
2
 
4
3
  module ActiveRecord
@@ -6,7 +5,7 @@ module ActiveRecord
6
5
  ONE_AS_ONE = '1 AS one'
7
6
 
8
7
  # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
9
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
8
+ # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key
10
9
  # is an integer, find by id coerces its arguments using +to_i+.
11
10
  #
12
11
  # Person.find(1) # returns the object for ID = 1
@@ -17,10 +16,8 @@ module ActiveRecord
17
16
  # Person.find([1]) # returns an array for the object with ID = 1
18
17
  # Person.where("administrator = 1").order("created_on DESC").find(1)
19
18
  #
20
- # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found.
21
- #
22
19
  # NOTE: The returned records may not be in the same order as the ids you
23
- # provide since database rows are unordered. You'd need to provide an explicit <tt>order</tt>
20
+ # provide since database rows are unordered. You'd need to provide an explicit QueryMethods#order
24
21
  # option if you want the results are sorted.
25
22
  #
26
23
  # ==== Find with lock
@@ -37,7 +34,7 @@ module ActiveRecord
37
34
  # person.save!
38
35
  # end
39
36
  #
40
- # ==== Variations of +find+
37
+ # ==== Variations of #find
41
38
  #
42
39
  # Person.where(name: 'Spartacus', rating: 4)
43
40
  # # returns a chainable list (which can be empty).
@@ -45,13 +42,13 @@ module ActiveRecord
45
42
  # Person.find_by(name: 'Spartacus', rating: 4)
46
43
  # # returns the first item or nil.
47
44
  #
48
- # Person.where(name: 'Spartacus', rating: 4).first_or_initialize
45
+ # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
49
46
  # # returns the first item or returns a new instance (requires you call .save to persist against the database).
50
47
  #
51
- # Person.where(name: 'Spartacus', rating: 4).first_or_create
52
- # # returns the first item or creates it and returns it, available since Rails 3.2.1.
48
+ # Person.find_or_create_by(name: 'Spartacus', rating: 4)
49
+ # # returns the first item or creates it and returns it.
53
50
  #
54
- # ==== Alternatives for +find+
51
+ # ==== Alternatives for #find
55
52
  #
56
53
  # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
57
54
  # # returns a boolean indicating if any record with the given conditions exist.
@@ -60,16 +57,13 @@ module ActiveRecord
60
57
  # # returns a chainable list of instances with only the mentioned fields.
61
58
  #
62
59
  # Person.where(name: 'Spartacus', rating: 4).ids
63
- # # returns an Array of ids, available since Rails 3.2.1.
60
+ # # returns an Array of ids.
64
61
  #
65
62
  # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
66
- # # returns an Array of the required fields, available since Rails 3.1.
63
+ # # returns an Array of the required fields.
67
64
  def find(*args)
68
- if block_given?
69
- to_a.find(*args) { |*block_args| yield(*block_args) }
70
- else
71
- find_with_ids(*args)
72
- end
65
+ return super if block_given?
66
+ find_with_ids(*args)
73
67
  end
74
68
 
75
69
  # Finds the first record matching the specified conditions. There
@@ -80,18 +74,19 @@ module ActiveRecord
80
74
  #
81
75
  # Post.find_by name: 'Spartacus', rating: 4
82
76
  # Post.find_by "published_at < ?", 2.weeks.ago
83
- def find_by(*args)
84
- where(*args).take
77
+ def find_by(arg, *args)
78
+ where(arg, *args).take
85
79
  rescue RangeError
86
80
  nil
87
81
  end
88
82
 
89
- # Like <tt>find_by</tt>, except that if no record is found, raises
90
- # an <tt>ActiveRecord::RecordNotFound</tt> error.
91
- def find_by!(*args)
92
- where(*args).take!
83
+ # Like #find_by, except that if no record is found, raises
84
+ # an ActiveRecord::RecordNotFound error.
85
+ def find_by!(arg, *args)
86
+ where(arg, *args).take!
93
87
  rescue RangeError
94
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
88
+ raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
89
+ @klass.name)
95
90
  end
96
91
 
97
92
  # Gives a record (or N records if a parameter is supplied) without any implied
@@ -105,43 +100,31 @@ module ActiveRecord
105
100
  limit ? limit(limit).to_a : find_take
106
101
  end
107
102
 
108
- # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
109
- # is found. Note that <tt>take!</tt> accepts no arguments.
103
+ # Same as #take but raises ActiveRecord::RecordNotFound if no record
104
+ # is found. Note that #take! accepts no arguments.
110
105
  def take!
111
- take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
106
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
112
107
  end
113
108
 
114
109
  # Find the first record (or first N records if a parameter is supplied).
115
110
  # If no order is defined it will order by primary key.
116
111
  #
117
- # Person.first # returns the first object fetched by SELECT * FROM people
112
+ # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
118
113
  # Person.where(["user_name = ?", user_name]).first
119
114
  # Person.where(["user_name = :u", { u: user_name }]).first
120
115
  # Person.order("created_on DESC").offset(5).first
121
- # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
122
- #
123
- # ==== Rails 3
124
- #
125
- # Person.first # SELECT "people".* FROM "people" LIMIT 1
126
- #
127
- # NOTE: Rails 3 may not order this query by the primary key and the order
128
- # will depend on the database implementation. In order to ensure that behavior,
129
- # use <tt>User.order(:id).first</tt> instead.
130
- #
131
- # ==== Rails 4
132
- #
133
- # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1
116
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
134
117
  #
135
118
  def first(limit = nil)
136
119
  if limit
137
- find_nth_with_limit(offset_index, limit)
120
+ find_nth_with_limit_and_offset(0, limit, offset: offset_index)
138
121
  else
139
- find_nth(0, offset_index)
122
+ find_nth 0
140
123
  end
141
124
  end
142
125
 
143
- # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
144
- # is found. Note that <tt>first!</tt> accepts no arguments.
126
+ # Same as #first but raises ActiveRecord::RecordNotFound if no record
127
+ # is found. Note that #first! accepts no arguments.
145
128
  def first!
146
129
  find_nth! 0
147
130
  end
@@ -162,21 +145,27 @@ module ActiveRecord
162
145
  #
163
146
  # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
164
147
  def last(limit = nil)
165
- if limit
166
- if order_values.empty? && primary_key
167
- order(arel_table[primary_key].desc).limit(limit).reverse
168
- else
169
- to_a.last(limit)
170
- end
171
- else
172
- find_last
173
- end
148
+ return find_last(limit) if loaded? || limit_value
149
+
150
+ result = limit(limit || 1)
151
+ result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
152
+ result = result.reverse_order!
153
+
154
+ limit ? result.reverse : result.first
155
+ rescue ActiveRecord::IrreversibleOrderError
156
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
157
+ Finding a last element by loading the relation when SQL ORDER
158
+ can not be reversed is deprecated.
159
+ Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
160
+ Please call `to_a.last` if you still want to load the relation.
161
+ WARNING
162
+ find_last(limit)
174
163
  end
175
164
 
176
- # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
177
- # is found. Note that <tt>last!</tt> accepts no arguments.
165
+ # Same as #last but raises ActiveRecord::RecordNotFound if no record
166
+ # is found. Note that #last! accepts no arguments.
178
167
  def last!
179
- last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
168
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
180
169
  end
181
170
 
182
171
  # Find the second record.
@@ -186,10 +175,10 @@ module ActiveRecord
186
175
  # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
187
176
  # Person.where(["user_name = :u", { u: user_name }]).second
188
177
  def second
189
- find_nth(1, offset_index)
178
+ find_nth 1
190
179
  end
191
180
 
192
- # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
181
+ # Same as #second but raises ActiveRecord::RecordNotFound if no record
193
182
  # is found.
194
183
  def second!
195
184
  find_nth! 1
@@ -202,10 +191,10 @@ module ActiveRecord
202
191
  # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
203
192
  # Person.where(["user_name = :u", { u: user_name }]).third
204
193
  def third
205
- find_nth(2, offset_index)
194
+ find_nth 2
206
195
  end
207
196
 
208
- # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
197
+ # Same as #third but raises ActiveRecord::RecordNotFound if no record
209
198
  # is found.
210
199
  def third!
211
200
  find_nth! 2
@@ -218,10 +207,10 @@ module ActiveRecord
218
207
  # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
219
208
  # Person.where(["user_name = :u", { u: user_name }]).fourth
220
209
  def fourth
221
- find_nth(3, offset_index)
210
+ find_nth 3
222
211
  end
223
212
 
224
- # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
213
+ # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
225
214
  # is found.
226
215
  def fourth!
227
216
  find_nth! 3
@@ -234,10 +223,10 @@ module ActiveRecord
234
223
  # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
235
224
  # Person.where(["user_name = :u", { u: user_name }]).fifth
236
225
  def fifth
237
- find_nth(4, offset_index)
226
+ find_nth 4
238
227
  end
239
228
 
240
- # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
229
+ # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
241
230
  # is found.
242
231
  def fifth!
243
232
  find_nth! 4
@@ -250,17 +239,49 @@ module ActiveRecord
250
239
  # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
251
240
  # Person.where(["user_name = :u", { u: user_name }]).forty_two
252
241
  def forty_two
253
- find_nth(41, offset_index)
242
+ find_nth 41
254
243
  end
255
244
 
256
- # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
245
+ # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
257
246
  # is found.
258
247
  def forty_two!
259
248
  find_nth! 41
260
249
  end
261
250
 
262
- # Returns +true+ if a record exists in the table that matches the +id+ or
263
- # conditions given, or +false+ otherwise. The argument can take six forms:
251
+ # Find the third-to-last record.
252
+ # If no order is defined it will order by primary key.
253
+ #
254
+ # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
255
+ # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
256
+ # Person.where(["user_name = :u", { u: user_name }]).third_to_last
257
+ def third_to_last
258
+ find_nth_from_last 3
259
+ end
260
+
261
+ # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
262
+ # is found.
263
+ def third_to_last!
264
+ find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
265
+ end
266
+
267
+ # Find the second-to-last record.
268
+ # If no order is defined it will order by primary key.
269
+ #
270
+ # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
271
+ # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
272
+ # Person.where(["user_name = :u", { u: user_name }]).second_to_last
273
+ def second_to_last
274
+ find_nth_from_last 2
275
+ end
276
+
277
+ # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
278
+ # is found.
279
+ def second_to_last!
280
+ find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
281
+ end
282
+
283
+ # Returns true if a record exists in the table that matches the +id+ or
284
+ # conditions given, or false otherwise. The argument can take six forms:
264
285
  #
265
286
  # * Integer - Finds the record with this primary key.
266
287
  # * String - Finds the record with a primary key corresponding to this
@@ -273,7 +294,7 @@ module ActiveRecord
273
294
  # * No args - Returns +false+ if the table is empty, +true+ otherwise.
274
295
  #
275
296
  # For more information about specifying conditions as a hash or array,
276
- # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
297
+ # see the Conditions section in the introduction to ActiveRecord::Base.
277
298
  #
278
299
  # Note: You can't pass in a condition as a string (like <tt>name =
279
300
  # 'Jamie'</tt>), since it would be sanitized and then queried against
@@ -291,7 +312,7 @@ module ActiveRecord
291
312
  conditions = conditions.id
292
313
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
293
314
  You are passing an instance of ActiveRecord::Base to `exists?`.
294
- Please pass the id of the object by calling `.id`
315
+ Please pass the id of the object by calling `.id`.
295
316
  MSG
296
317
  end
297
318
 
@@ -307,15 +328,15 @@ module ActiveRecord
307
328
  relation = relation.where(conditions)
308
329
  else
309
330
  unless conditions == :none
310
- relation = where(primary_key => conditions)
331
+ relation = relation.where(primary_key => conditions)
311
332
  end
312
333
  end
313
334
 
314
- connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
335
+ connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
315
336
  end
316
337
 
317
338
  # This method is called whenever no records are found with either a single
318
- # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
339
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
319
340
  #
320
341
  # The error message is different depending on whether a single id or
321
342
  # multiple ids are provided. If multiple ids are provided, then the number
@@ -323,7 +344,7 @@ module ActiveRecord
323
344
  # the expected number of results should be provided in the +expected_size+
324
345
  # argument.
325
346
  def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
326
- conditions = arel.where_sql
347
+ conditions = arel.where_sql(@klass.arel_engine)
327
348
  conditions = " [#{conditions}]" if conditions
328
349
 
329
350
  if Array(ids).size == 1
@@ -365,7 +386,7 @@ module ActiveRecord
365
386
  []
366
387
  else
367
388
  arel = relation.arel
368
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
389
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
369
390
  join_dependency.instantiate(rows, aliases)
370
391
  end
371
392
  end
@@ -379,7 +400,7 @@ module ActiveRecord
379
400
  def construct_relation_for_association_calculations
380
401
  from = arel.froms.first
381
402
  if Arel::Table === from
382
- apply_join_dependency(self, construct_join_dependency)
403
+ apply_join_dependency(self, construct_join_dependency(joins_values))
383
404
  else
384
405
  # FIXME: as far as I can tell, `from` will always be an Arel::Table.
385
406
  # There are no tests that test this branch, but presumably it's
@@ -397,7 +418,7 @@ module ActiveRecord
397
418
  else
398
419
  if relation.limit_value
399
420
  limited_ids = limited_ids_for(relation)
400
- limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
421
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
401
422
  end
402
423
  relation.except(:limit, :offset)
403
424
  end
@@ -410,12 +431,12 @@ module ActiveRecord
410
431
  relation = relation.except(:select).select(values).distinct!
411
432
  arel = relation.arel
412
433
 
413
- id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
434
+ id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
414
435
  id_rows.map {|row| row[primary_key]}
415
436
  end
416
437
 
417
438
  def using_limitable_reflections?(reflections)
418
- reflections.none? { |r| r.collection? }
439
+ reflections.none?(&:collection?)
419
440
  end
420
441
 
421
442
  protected
@@ -446,7 +467,7 @@ module ActiveRecord
446
467
  id = id.id
447
468
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
448
469
  You are passing an instance of ActiveRecord::Base to `find`.
449
- Please pass the id of the object by calling `.id`
470
+ Please pass the id of the object by calling `.id`.
450
471
  MSG
451
472
  end
452
473
 
@@ -459,6 +480,8 @@ module ActiveRecord
459
480
  end
460
481
 
461
482
  def find_some(ids)
483
+ return find_some_ordered(ids) unless order_values.present?
484
+
462
485
  result = where(primary_key => ids).to_a
463
486
 
464
487
  expected_size =
@@ -480,49 +503,96 @@ module ActiveRecord
480
503
  end
481
504
  end
482
505
 
506
+ def find_some_ordered(ids)
507
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
508
+
509
+ result = except(:limit, :offset).where(primary_key => ids).records
510
+
511
+ if result.size == ids.size
512
+ pk_type = @klass.type_for_attribute(primary_key)
513
+
514
+ records_by_id = result.index_by(&:id)
515
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
516
+ else
517
+ raise_record_not_found_exception!(ids, result.size, ids.size)
518
+ end
519
+ end
520
+
483
521
  def find_take
484
522
  if loaded?
485
523
  @records.first
486
524
  else
487
- @take ||= limit(1).to_a.first
525
+ @take ||= limit(1).records.first
488
526
  end
489
527
  end
490
528
 
491
- def find_nth(index, offset)
529
+ def find_nth(index, offset = nil)
530
+ # TODO: once the offset argument is removed we rely on offset_index
531
+ # within find_nth_with_limit, rather than pass it in via
532
+ # find_nth_with_limit_and_offset
533
+ if offset
534
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
535
+ Passing an offset argument to find_nth is deprecated,
536
+ please use Relation#offset instead.
537
+ MSG
538
+ end
492
539
  if loaded?
493
540
  @records[index]
494
541
  else
495
- offset += index
496
- @offsets[offset] ||= find_nth_with_limit(offset, 1).first
542
+ offset ||= offset_index
543
+ @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
497
544
  end
498
545
  end
499
546
 
500
547
  def find_nth!(index)
501
- find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
548
+ find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
502
549
  end
503
550
 
504
- def find_nth_with_limit(offset, limit)
551
+ def find_nth_with_limit(index, limit)
552
+ # TODO: once the offset argument is removed from find_nth,
553
+ # find_nth_with_limit_and_offset can be merged into this method
505
554
  relation = if order_values.empty? && primary_key
506
- order(arel_table[primary_key].asc)
555
+ order(arel_attribute(primary_key).asc)
507
556
  else
508
557
  self
509
558
  end
510
559
 
511
- relation = relation.offset(offset) unless offset.zero?
560
+ relation = relation.offset(index) unless index.zero?
512
561
  relation.limit(limit).to_a
513
562
  end
514
563
 
515
- def find_last
564
+ def find_nth_from_last(index)
565
+ if loaded?
566
+ @records[-index]
567
+ else
568
+ relation = if order_values.empty? && primary_key
569
+ order(arel_attribute(primary_key).asc)
570
+ else
571
+ self
572
+ end
573
+
574
+ relation.to_a[-index]
575
+ # TODO: can be made more performant on large result sets by
576
+ # for instance, last(index)[-index] (which would require
577
+ # refactoring the last(n) finder method to make test suite pass),
578
+ # or by using a combination of reverse_order, limit, and offset,
579
+ # e.g., reverse_order.offset(index-1).first
580
+ end
581
+ end
582
+
583
+ private
584
+
585
+ def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
516
586
  if loaded?
517
- @records.last
587
+ @records[index, limit]
518
588
  else
519
- @last ||=
520
- if limit_value
521
- to_a.last
522
- else
523
- reverse_order.limit(1).to_a.first
524
- end
589
+ index += offset
590
+ find_nth_with_limit(index, limit)
525
591
  end
526
592
  end
593
+
594
+ def find_last(limit)
595
+ limit ? records.last(limit) : records.last
596
+ end
527
597
  end
528
598
  end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ class FromClause # :nodoc:
4
+ attr_reader :value, :name
5
+
6
+ def initialize(value, name)
7
+ @value = value
8
+ @name = name
9
+ end
10
+
11
+ def binds
12
+ if value.is_a?(Relation)
13
+ value.bound_attributes
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ def merge(other)
20
+ self
21
+ end
22
+
23
+ def empty?
24
+ value.nil?
25
+ end
26
+
27
+ def self.empty
28
+ @empty ||= new(nil, nil)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,4 @@
1
1
  require 'active_support/core_ext/hash/keys'
2
- require "set"
3
2
 
4
3
  module ActiveRecord
5
4
  class Relation
@@ -22,7 +21,7 @@ module ActiveRecord
22
21
  # build a relation to merge in rather than directly merging
23
22
  # the values.
24
23
  def other
25
- other = Relation.create(relation.klass, relation.table)
24
+ other = Relation.create(relation.klass, relation.table, relation.predicate_builder)
26
25
  hash.each { |k, v|
27
26
  if k == :joins
28
27
  if Hash === v
@@ -49,9 +48,9 @@ module ActiveRecord
49
48
  @other = other
50
49
  end
51
50
 
52
- NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
53
- Relation::MULTI_VALUE_METHODS -
54
- [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
51
+ NORMAL_VALUES = Relation::VALUE_METHODS -
52
+ Relation::CLAUSE_METHODS -
53
+ [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
55
54
 
56
55
  def normal_values
57
56
  NORMAL_VALUES
@@ -75,6 +74,8 @@ module ActiveRecord
75
74
 
76
75
  merge_multi_values
77
76
  merge_single_values
77
+ merge_clauses
78
+ merge_preloads
78
79
  merge_joins
79
80
 
80
81
  relation
@@ -82,6 +83,27 @@ module ActiveRecord
82
83
 
83
84
  private
84
85
 
86
+ def merge_preloads
87
+ return if other.preload_values.empty? && other.includes_values.empty?
88
+
89
+ if other.klass == relation.klass
90
+ relation.preload!(*other.preload_values) unless other.preload_values.empty?
91
+ relation.includes!(other.includes_values) unless other.includes_values.empty?
92
+ else
93
+ reflection = relation.klass.reflect_on_all_associations.find do |r|
94
+ r.class_name == other.klass.name
95
+ end || return
96
+
97
+ unless other.preload_values.empty?
98
+ relation.preload! reflection.name => other.preload_values
99
+ end
100
+
101
+ unless other.includes_values.empty?
102
+ relation.includes! reflection.name => other.includes_values
103
+ end
104
+ end
105
+ end
106
+
85
107
  def merge_joins
86
108
  return if other.joins_values.blank?
87
109
 
@@ -107,20 +129,6 @@ module ActiveRecord
107
129
  end
108
130
 
109
131
  def merge_multi_values
110
- lhs_wheres = relation.where_values
111
- rhs_wheres = other.where_values
112
-
113
- lhs_binds = relation.bind_values
114
- rhs_binds = other.bind_values
115
-
116
- removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
117
-
118
- where_values = kept + rhs_wheres
119
- bind_values = filter_binds(lhs_binds, removed) + rhs_binds
120
-
121
- relation.where_values = where_values
122
- relation.bind_values = bind_values
123
-
124
132
  if other.reordering_value
125
133
  # override any order specified in the original relation
126
134
  relation.reorder! other.order_values
@@ -133,36 +141,25 @@ module ActiveRecord
133
141
  end
134
142
 
135
143
  def merge_single_values
136
- relation.from_value = other.from_value unless relation.from_value
137
- relation.lock_value = other.lock_value unless relation.lock_value
144
+ if relation.from_clause.empty?
145
+ relation.from_clause = other.from_clause
146
+ end
147
+ relation.lock_value ||= other.lock_value
138
148
 
139
149
  unless other.create_with_value.blank?
140
150
  relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
141
151
  end
142
152
  end
143
153
 
144
- def filter_binds(lhs_binds, removed_wheres)
145
- return lhs_binds if removed_wheres.empty?
146
-
147
- set = Set.new removed_wheres.map { |x| x.left.name.to_s }
148
- lhs_binds.dup.delete_if { |col,_| set.include? col.name }
154
+ CLAUSE_METHOD_NAMES = CLAUSE_METHODS.map do |name|
155
+ ["#{name}_clause", "#{name}_clause="]
149
156
  end
150
157
 
151
- # Remove equalities from the existing relation with a LHS which is
152
- # present in the relation being merged in.
153
- # returns [things_to_remove, things_to_keep]
154
- def partition_overwrites(lhs_wheres, rhs_wheres)
155
- if lhs_wheres.empty? || rhs_wheres.empty?
156
- return [[], lhs_wheres]
157
- end
158
-
159
- nodes = rhs_wheres.find_all do |w|
160
- w.respond_to?(:operator) && w.operator == :==
161
- end
162
- seen = Set.new(nodes) { |node| node.left }
163
-
164
- lhs_wheres.partition do |w|
165
- w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
158
+ def merge_clauses
159
+ CLAUSE_METHOD_NAMES.each do |(reader, writer)|
160
+ clause = relation.send(reader)
161
+ other_clause = other.send(reader)
162
+ relation.send(writer, clause.merge(other_clause))
166
163
  end
167
164
  end
168
165
  end