activerecord 3.2.22.4 → 4.0.13

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

Potentially problematic release.


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

Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,85 +1,22 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/hash/indifferent_access'
3
-
4
1
  module ActiveRecord
5
2
  module FinderMethods
6
- # Find operates with four different retrieval approaches:
7
- #
8
- # * 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.
10
- # * Find first - This will return the first record matched by the options used. These options can either be specific
11
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
12
- # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
13
- # * Find last - This will return the last record matched by the options used. These options can either be specific
14
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
15
- # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
16
- # * Find all - This will return all the records matched by the options used.
17
- # If no records are found, an empty array is returned. Use
18
- # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
19
- #
20
- # All approaches accept an options hash as their last parameter.
21
- #
22
- # ==== Options
23
- #
24
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>["user_name = ?", username]</tt>,
25
- # or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
26
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
27
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
28
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
29
- # <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
30
- # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
31
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
32
- # it would skip rows 0 through 4.
33
- # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
34
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an
35
- # <tt>INNER JOIN</tt> on the associated table(s),
36
- # or an array containing a mixture of both strings and named associations.
37
- # If the value is a string, then the records will be returned read-only since they will
38
- # have attributes that do not correspond to the table's columns.
39
- # Pass <tt>:readonly => false</tt> to override.
40
- # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
41
- # to already defined associations. See eager loading under Associations.
42
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
43
- # for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
44
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
45
- # to an alternate table name (or even the name of a database view).
46
- # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
47
- # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
48
- # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
49
- #
50
- # ==== Examples
3
+ # 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]).
4
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
5
+ # is an integer, find by id coerces its arguments using +to_i+.
51
6
  #
52
- # # find by id
53
- # Person.find(1) # returns the object for ID = 1
54
- # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
55
- # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
56
- # Person.find([1]) # returns an array for the object with ID = 1
7
+ # Person.find(1) # returns the object for ID = 1
8
+ # Person.find("1") # returns the object for ID = 1
9
+ # Person.find("31-sarah") # returns the object for ID = 31
10
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
11
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
12
+ # Person.find([1]) # returns an array for the object with ID = 1
57
13
  # Person.where("administrator = 1").order("created_on DESC").find(1)
58
14
  #
59
15
  # Note that returned records may not be in the same order as the ids you
60
- # provide since database rows are unordered. Give an explicit <tt>:order</tt>
16
+ # provide since database rows are unordered. Give an explicit <tt>order</tt>
61
17
  # to ensure the results are sorted.
62
18
  #
63
- # ==== Examples
64
- #
65
- # # find first
66
- # Person.first # returns the first object fetched by SELECT * FROM people
67
- # Person.where(["user_name = ?", user_name]).first
68
- # Person.where(["user_name = :u", { :u => user_name }]).first
69
- # Person.order("created_on DESC").offset(5).first
70
- #
71
- # # find last
72
- # Person.last # returns the last object fetched by SELECT * FROM people
73
- # Person.where(["user_name = ?", user_name]).last
74
- # Person.order("created_on DESC").offset(5).last
75
- #
76
- # # find all
77
- # Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
78
- # Person.where(["category IN (?)", categories]).limit(50).all
79
- # Person.where({ :friends => ["Bob", "Steve", "Fred"] }).all
80
- # Person.offset(10).limit(10).all
81
- # Person.includes([:account, :friends]).all
82
- # Person.group("category").all
19
+ # ==== Find with lock
83
20
  #
84
21
  # Example for find with a lock: Imagine two concurrent transactions:
85
22
  # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
@@ -93,30 +30,62 @@ module ActiveRecord
93
30
  # person.save!
94
31
  # end
95
32
  def find(*args)
96
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
97
-
98
- options = args.extract_options!
99
-
100
- if options.present?
101
- apply_finder_options(options).find(*args)
33
+ if block_given?
34
+ to_a.find(*args) { |*block_args| yield(*block_args) }
102
35
  else
103
- case args.first
104
- when :first, :last, :all
105
- send(args.first)
106
- else
107
- find_with_ids(*args)
108
- end
36
+ find_with_ids(*args)
109
37
  end
110
38
  end
111
39
 
112
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
113
- # same arguments to this method as you can to <tt>find(:first)</tt>.
114
- def first(*args)
115
- if args.any?
116
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
117
- limit(*args).to_a
40
+ # Finds the first record matching the specified conditions. There
41
+ # is no implied ordering so if order matters, you should specify it
42
+ # yourself.
43
+ #
44
+ # If no record is found, returns <tt>nil</tt>.
45
+ #
46
+ # Post.find_by name: 'Spartacus', rating: 4
47
+ # Post.find_by "published_at < ?", 2.weeks.ago
48
+ def find_by(*args)
49
+ where(*args).take
50
+ end
51
+
52
+ # Like <tt>find_by</tt>, except that if no record is found, raises
53
+ # an <tt>ActiveRecord::RecordNotFound</tt> error.
54
+ def find_by!(*args)
55
+ where(*args).take!
56
+ end
57
+
58
+ # Gives a record (or N records if a parameter is supplied) without any implied
59
+ # order. The order will depend on the database implementation.
60
+ # If an order is supplied it will be respected.
61
+ #
62
+ # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
63
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
64
+ # Person.where(["name LIKE '%?'", name]).take
65
+ def take(limit = nil)
66
+ limit ? limit(limit).to_a : find_take
67
+ end
68
+
69
+ # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
70
+ # is found. Note that <tt>take!</tt> accepts no arguments.
71
+ def take!
72
+ take or raise RecordNotFound
73
+ end
74
+
75
+ # Find the first record (or first N records if a parameter is supplied).
76
+ # If no order is defined it will order by primary key.
77
+ #
78
+ # Person.first # returns the first object fetched by SELECT * FROM people
79
+ # Person.where(["user_name = ?", user_name]).first
80
+ # Person.where(["user_name = :u", { u: user_name }]).first
81
+ # Person.order("created_on DESC").offset(5).first
82
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
83
+ def first(limit = nil)
84
+ if limit
85
+ if order_values.empty? && primary_key
86
+ order(arel_table[primary_key].asc).limit(limit).to_a
118
87
  else
119
- apply_finder_options(args.first).first
88
+ limit(limit).to_a
120
89
  end
121
90
  else
122
91
  find_first
@@ -129,18 +98,27 @@ module ActiveRecord
129
98
  first or raise RecordNotFound
130
99
  end
131
100
 
132
- # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
133
- # same arguments to this method as you can to <tt>find(:last)</tt>.
134
- def last(*args)
135
- if args.any?
136
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
137
- if order_values.empty? && primary_key
138
- order("#{quoted_table_name}.#{quoted_primary_key} DESC").limit(*args).reverse
139
- else
140
- to_a.last(*args)
141
- end
101
+ # Find the last record (or last N records if a parameter is supplied).
102
+ # If no order is defined it will order by primary key.
103
+ #
104
+ # Person.last # returns the last object fetched by SELECT * FROM people
105
+ # Person.where(["user_name = ?", user_name]).last
106
+ # Person.order("created_on DESC").offset(5).last
107
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
108
+ #
109
+ # Take note that in that last case, the results are sorted in ascending order:
110
+ #
111
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
112
+ #
113
+ # and not:
114
+ #
115
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
116
+ def last(limit = nil)
117
+ if limit
118
+ if order_values.empty? && primary_key
119
+ order(arel_table[primary_key].desc).limit(limit).reverse
142
120
  else
143
- apply_finder_options(args.first).last
121
+ to_a.last(limit)
144
122
  end
145
123
  else
146
124
  find_last
@@ -153,57 +131,74 @@ module ActiveRecord
153
131
  last or raise RecordNotFound
154
132
  end
155
133
 
156
- # A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
157
- # same arguments to this method as you can to <tt>find(:all)</tt>.
158
- def all(*args)
159
- args.any? ? apply_finder_options(args.first).to_a : to_a
160
- end
161
-
162
- # Returns true if a record exists in the table that matches the +id+ or
163
- # conditions given, or false otherwise. The argument can take five forms:
134
+ # Returns +true+ if a record exists in the table that matches the +id+ or
135
+ # conditions given, or +false+ otherwise. The argument can take six forms:
164
136
  #
165
137
  # * Integer - Finds the record with this primary key.
166
138
  # * String - Finds the record with a primary key corresponding to this
167
139
  # string (such as <tt>'5'</tt>).
168
140
  # * Array - Finds the record that matches these +find+-style conditions
169
- # (such as <tt>['color = ?', 'red']</tt>).
141
+ # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
170
142
  # * Hash - Finds the record that matches these +find+-style conditions
171
- # (such as <tt>{:color => 'red'}</tt>).
172
- # * No args - Returns false if the table is empty, true otherwise.
143
+ # (such as <tt>{name: 'David'}</tt>).
144
+ # * +false+ - Returns always +false+.
145
+ # * No args - Returns +false+ if the table is empty, +true+ otherwise.
173
146
  #
174
- # For more information about specifying conditions as a Hash or Array,
175
- # see the Conditions section in the introduction to ActiveRecord::Base.
147
+ # For more information about specifying conditions as a hash or array,
148
+ # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
176
149
  #
177
150
  # Note: You can't pass in a condition as a string (like <tt>name =
178
151
  # 'Jamie'</tt>), since it would be sanitized and then queried against
179
152
  # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
180
153
  #
181
- # ==== Examples
182
154
  # Person.exists?(5)
183
155
  # Person.exists?('5')
184
- # Person.exists?(:name => "David")
185
156
  # Person.exists?(['name LIKE ?', "%#{query}%"])
157
+ # Person.exists?(name: 'David')
158
+ # Person.exists?(false)
186
159
  # Person.exists?
187
- def exists?(id = false)
188
- id = id.id if ActiveRecord::Base === id
189
- return false if id.nil?
160
+ def exists?(conditions = :none)
161
+ conditions = conditions.id if Base === conditions
162
+ return false if !conditions
190
163
 
191
164
  join_dependency = construct_join_dependency_for_association_find
192
165
  relation = construct_relation_for_association_find(join_dependency)
193
166
  relation = relation.except(:select, :order).select("1 AS one").limit(1)
194
167
 
195
- case id
168
+ case conditions
196
169
  when Array, Hash
197
- relation = relation.where(id)
170
+ relation = relation.where(conditions)
198
171
  else
199
- relation = relation.where(table[primary_key].eq(id)) if id
172
+ relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
200
173
  end
201
174
 
202
- connection.select_value(relation, "#{name} Exists") ? true : false
175
+ connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
203
176
  rescue ThrowResult
204
177
  false
205
178
  end
206
179
 
180
+ # This method is called whenever no records are found with either a single
181
+ # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
182
+ #
183
+ # The error message is different depending on whether a single id or
184
+ # multiple ids are provided. If multiple ids are provided, then the number
185
+ # of results obtained should be provided in the +result_size+ argument and
186
+ # the expected number of results should be provided in the +expected_size+
187
+ # argument.
188
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
189
+ conditions = arel.where_sql
190
+ conditions = " [#{conditions}]" if conditions
191
+
192
+ if Array(ids).size == 1
193
+ error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
194
+ else
195
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
196
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
197
+ end
198
+
199
+ raise RecordNotFound, error
200
+ end
201
+
207
202
  protected
208
203
 
209
204
  def find_with_associations
@@ -216,19 +211,19 @@ module ActiveRecord
216
211
  end
217
212
 
218
213
  def construct_join_dependency_for_association_find
219
- including = (@eager_load_values + @includes_values).uniq
220
- ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
214
+ including = (eager_load_values + includes_values).uniq
215
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, joins_values)
221
216
  end
222
217
 
223
218
  def construct_relation_for_association_calculations
224
- including = (@eager_load_values + @includes_values).uniq
219
+ including = (eager_load_values + includes_values).uniq
225
220
  join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
226
221
  relation = except(:includes, :eager_load, :preload)
227
222
  apply_join_dependency(relation, join_dependency)
228
223
  end
229
224
 
230
225
  def construct_relation_for_association_find(join_dependency)
231
- relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
226
+ relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns + select_values)
232
227
  apply_join_dependency(relation, join_dependency)
233
228
  end
234
229
 
@@ -251,10 +246,9 @@ module ActiveRecord
251
246
 
252
247
  def construct_limited_ids_condition(relation)
253
248
  orders = relation.order_values.map { |val| val.presence }.compact
254
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
249
+ values = @klass.connection.columns_for_distinct("#{quoted_table_name}.#{quoted_primary_key}", orders)
255
250
 
256
- relation = relation.dup.select(values)
257
- relation.uniq_value = nil
251
+ relation = relation.dup.select(values).distinct!
258
252
 
259
253
  id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
260
254
  ids_array = id_rows.map {|row| row[primary_key]}
@@ -262,47 +256,7 @@ module ActiveRecord
262
256
  ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
263
257
  end
264
258
 
265
- def find_by_attributes(match, attributes, *args)
266
- conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
267
- result = where(conditions).send(match.finder)
268
-
269
- if match.bang? && result.nil?
270
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
271
- else
272
- yield(result) if block_given?
273
- result
274
- end
275
- end
276
-
277
- def find_or_instantiator_by_attributes(match, attributes, *args)
278
- options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
279
- protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
280
- args.each_with_index do |arg, i|
281
- if arg.is_a?(Hash)
282
- protected_attributes_for_create = args[i].with_indifferent_access
283
- else
284
- unprotected_attributes_for_create[attributes[i]] = args[i]
285
- end
286
- end
287
-
288
- conditions = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes).symbolize_keys
289
-
290
- record = where(conditions).first
291
-
292
- unless record
293
- record = @klass.new(protected_attributes_for_create, options) do |r|
294
- r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
295
- end
296
- yield(record) if block_given?
297
- record.send(match.save_method) if match.save_record?
298
- end
299
-
300
- record
301
- end
302
-
303
259
  def find_with_ids(*ids)
304
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
305
-
306
260
  expects_array = ids.first.kind_of?(Array)
307
261
  return ids.first if expects_array && ids.first.empty?
308
262
 
@@ -322,55 +276,44 @@ module ActiveRecord
322
276
  def find_one(id)
323
277
  id = id.id if ActiveRecord::Base === id
324
278
 
325
- if IdentityMap.enabled? && where_values.blank? &&
326
- limit_value.blank? && order_values.blank? &&
327
- includes_values.blank? && preload_values.blank? &&
328
- readonly_value.nil? && joins_values.blank? &&
329
- !@klass.locking_enabled? &&
330
- record = IdentityMap.get(@klass, id)
331
- return record
332
- end
333
-
334
279
  column = columns_hash[primary_key]
335
-
336
- substitute = connection.substitute_at(column, @bind_values.length)
280
+ substitute = connection.substitute_at(column, bind_values.length)
337
281
  relation = where(table[primary_key].eq(substitute))
338
- relation.bind_values = [[column, id]]
339
- record = relation.first
282
+ relation.bind_values += [[column, id]]
283
+ record = relation.take
340
284
 
341
- unless record
342
- conditions = arel.where_sql
343
- conditions = " [#{conditions}]" if conditions
344
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
345
- end
285
+ raise_record_not_found_exception!(id, 0, 1) unless record
346
286
 
347
287
  record
348
288
  end
349
289
 
350
290
  def find_some(ids)
351
- result = where(table[primary_key].in(ids)).all
291
+ result = where(table[primary_key].in(ids)).to_a
352
292
 
353
293
  expected_size =
354
- if @limit_value && ids.size > @limit_value
355
- @limit_value
294
+ if limit_value && ids.size > limit_value
295
+ limit_value
356
296
  else
357
297
  ids.size
358
298
  end
359
299
 
360
300
  # 11 ids with limit 3, offset 9 should give 2 results.
361
- if @offset_value && (ids.size - @offset_value < expected_size)
362
- expected_size = ids.size - @offset_value
301
+ if offset_value && (ids.size - offset_value < expected_size)
302
+ expected_size = ids.size - offset_value
363
303
  end
364
304
 
365
305
  if result.size == expected_size
366
306
  result
367
307
  else
368
- conditions = arel.where_sql
369
- conditions = " [#{conditions}]" if conditions
308
+ raise_record_not_found_exception!(ids, result.size, expected_size)
309
+ end
310
+ end
370
311
 
371
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
372
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
373
- raise RecordNotFound, error
312
+ def find_take
313
+ if loaded?
314
+ @records.first
315
+ else
316
+ @take ||= limit(1).to_a.first
374
317
  end
375
318
  end
376
319
 
@@ -378,7 +321,12 @@ module ActiveRecord
378
321
  if loaded?
379
322
  @records.first
380
323
  else
381
- @first ||= limit(1).to_a[0]
324
+ @first ||=
325
+ if with_default_scope.order_values.empty? && primary_key
326
+ order(arel_table[primary_key].asc).limit(1).to_a.first
327
+ else
328
+ limit(1).to_a.first
329
+ end
382
330
  end
383
331
  end
384
332
 
@@ -390,7 +338,7 @@ module ActiveRecord
390
338
  if offset_value || limit_value
391
339
  to_a.last
392
340
  else
393
- reverse_order.limit(1).to_a[0]
341
+ reverse_order.limit(1).to_a.first
394
342
  end
395
343
  end
396
344
  end
@@ -0,0 +1,188 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require "set"
3
+
4
+ module ActiveRecord
5
+ class Relation
6
+ class HashMerger # :nodoc:
7
+ attr_reader :relation, :hash
8
+
9
+ def initialize(relation, hash)
10
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
11
+
12
+ @relation = relation
13
+ @hash = hash
14
+ end
15
+
16
+ def merge
17
+ Merger.new(relation, other).merge
18
+ end
19
+
20
+ # Applying values to a relation has some side effects. E.g.
21
+ # interpolation might take place for where values. So we should
22
+ # build a relation to merge in rather than directly merging
23
+ # the values.
24
+ def other
25
+ other = Relation.new(relation.klass, relation.table)
26
+ hash.each { |k, v|
27
+ if k == :joins
28
+ if Hash === v
29
+ other.joins!(v)
30
+ else
31
+ other.joins!(*v)
32
+ end
33
+ elsif k == :select
34
+ other._select!(v)
35
+ else
36
+ other.send("#{k}!", v)
37
+ end
38
+ }
39
+ other
40
+ end
41
+ end
42
+
43
+ class Merger # :nodoc:
44
+ attr_reader :relation, :values, :other
45
+
46
+ def initialize(relation, other)
47
+ if other.default_scoped? && other.klass != relation.klass
48
+ other = other.with_default_scope
49
+ end
50
+
51
+ @relation = relation
52
+ @values = other.values
53
+ @other = other
54
+ end
55
+
56
+ NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
57
+ Relation::MULTI_VALUE_METHODS -
58
+ [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
59
+
60
+ def normal_values
61
+ NORMAL_VALUES
62
+ end
63
+
64
+ def merge
65
+ normal_values.each do |name|
66
+ value = values[name]
67
+ # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
68
+ # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
69
+ # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
70
+ # don't fall through the cracks.
71
+ unless value.nil? || (value.blank? && false != value)
72
+ if name == :select
73
+ relation._select!(*value)
74
+ else
75
+ relation.send("#{name}!", *value)
76
+ end
77
+ end
78
+ end
79
+
80
+ merge_multi_values
81
+ merge_single_values
82
+ merge_joins
83
+
84
+ relation
85
+ end
86
+
87
+ private
88
+
89
+ def merge_joins
90
+ return if values[:joins].blank?
91
+
92
+ if other.klass == relation.klass
93
+ relation.joins!(*values[:joins])
94
+ else
95
+ joins_dependency, rest = values[:joins].partition do |join|
96
+ case join
97
+ when Hash, Symbol, Array
98
+ true
99
+ else
100
+ false
101
+ end
102
+ end
103
+
104
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
105
+ joins_dependency,
106
+ [])
107
+ relation.joins! rest
108
+
109
+ join_dependency.join_associations.each do |association|
110
+ @relation = association.join_relation(relation)
111
+ end
112
+ end
113
+ end
114
+
115
+ def merge_multi_values
116
+ lhs_wheres = relation.where_values
117
+ rhs_wheres = values[:where] || []
118
+
119
+ lhs_binds = relation.bind_values
120
+ rhs_binds = values[:bind] || []
121
+
122
+ removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
123
+
124
+ where_values = kept + rhs_wheres
125
+ bind_values = filter_binds(lhs_binds, removed) + rhs_binds
126
+
127
+ conn = relation.klass.connection
128
+ bv_index = 0
129
+ where_values.map! do |node|
130
+ if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right
131
+ substitute = conn.substitute_at(bind_values[bv_index].first, bv_index)
132
+ bv_index += 1
133
+ Arel::Nodes::Equality.new(node.left, substitute)
134
+ else
135
+ node
136
+ end
137
+ end
138
+
139
+ relation.where_values = where_values
140
+ relation.bind_values = bind_values
141
+
142
+ if values[:reordering]
143
+ # override any order specified in the original relation
144
+ relation.reorder! values[:order]
145
+ elsif values[:order]
146
+ # merge in order_values from r
147
+ relation.order! values[:order]
148
+ end
149
+
150
+ relation.extend(*values[:extending]) unless values[:extending].blank?
151
+ end
152
+
153
+ def merge_single_values
154
+ relation.from_value = values[:from] unless relation.from_value
155
+ relation.lock_value = values[:lock] unless relation.lock_value
156
+ relation.reverse_order_value = values[:reverse_order]
157
+
158
+ unless values[:create_with].blank?
159
+ relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
160
+ end
161
+ end
162
+
163
+ def filter_binds(lhs_binds, removed_wheres)
164
+ set = Set.new removed_wheres.map { |x| x.left.name.to_s }
165
+ lhs_binds.dup.delete_if { |col,_| set.include? col.name }
166
+ end
167
+
168
+ # Remove equalities from the existing relation with a LHS which is
169
+ # present in the relation being merged in.
170
+ # returns [things_to_remove, things_to_keep]
171
+ def partition_overwrites(lhs_wheres, rhs_wheres)
172
+ if lhs_wheres.empty? || rhs_wheres.empty?
173
+ return [[], lhs_wheres]
174
+ end
175
+
176
+ nodes = rhs_wheres.find_all do |w|
177
+ w.respond_to?(:operator) && w.operator == :==
178
+ end
179
+ seen = Set.new(nodes) { |node| node.left }
180
+
181
+ lhs_wheres.partition do |w|
182
+ w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
183
+ end
184
+ end
185
+
186
+ end
187
+ end
188
+ end