activerecord 4.2.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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. metadata +309 -0
@@ -0,0 +1,140 @@
1
+ require 'set'
2
+ require 'active_support/concern'
3
+ require 'active_support/deprecation'
4
+
5
+ module ActiveRecord
6
+ module Delegation # :nodoc:
7
+ module DelegateCache
8
+ def relation_delegate_class(klass) # :nodoc:
9
+ @relation_delegate_cache[klass]
10
+ end
11
+
12
+ def initialize_relation_delegate_cache # :nodoc:
13
+ @relation_delegate_cache = cache = {}
14
+ [
15
+ ActiveRecord::Relation,
16
+ ActiveRecord::Associations::CollectionProxy,
17
+ ActiveRecord::AssociationRelation
18
+ ].each do |klass|
19
+ delegate = Class.new(klass) {
20
+ include ClassSpecificRelation
21
+ }
22
+ const_set klass.name.gsub('::', '_'), delegate
23
+ cache[klass] = delegate
24
+ end
25
+ end
26
+
27
+ def inherited(child_class)
28
+ child_class.initialize_relation_delegate_cache
29
+ super
30
+ end
31
+ end
32
+
33
+ extend ActiveSupport::Concern
34
+
35
+ # This module creates compiled delegation methods dynamically at runtime, which makes
36
+ # subsequent calls to that method faster by avoiding method_missing. The delegations
37
+ # may vary depending on the klass of a relation, so we create a subclass of Relation
38
+ # for each different klass, and the delegations are compiled into that subclass only.
39
+
40
+ BLACKLISTED_ARRAY_METHODS = [
41
+ :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
42
+ :shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
43
+ :keep_if, :pop, :shift, :delete_at, :compact, :select!
44
+ ].to_set # :nodoc:
45
+
46
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
47
+
48
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
49
+ :connection, :columns_hash, :to => :klass
50
+
51
+ module ClassSpecificRelation # :nodoc:
52
+ extend ActiveSupport::Concern
53
+
54
+ included do
55
+ @delegation_mutex = Mutex.new
56
+ end
57
+
58
+ module ClassMethods # :nodoc:
59
+ def name
60
+ superclass.name
61
+ end
62
+
63
+ def delegate_to_scoped_klass(method)
64
+ @delegation_mutex.synchronize do
65
+ return if method_defined?(method)
66
+
67
+ if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
68
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
69
+ def #{method}(*args, &block)
70
+ scoping { @klass.#{method}(*args, &block) }
71
+ end
72
+ RUBY
73
+ else
74
+ define_method method do |*args, &block|
75
+ scoping { @klass.public_send(method, *args, &block) }
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def delegate(method, opts = {})
82
+ @delegation_mutex.synchronize do
83
+ return if method_defined?(method)
84
+ super
85
+ end
86
+ end
87
+ end
88
+
89
+ protected
90
+
91
+ def method_missing(method, *args, &block)
92
+ if @klass.respond_to?(method)
93
+ self.class.delegate_to_scoped_klass(method)
94
+ scoping { @klass.public_send(method, *args, &block) }
95
+ elsif arel.respond_to?(method)
96
+ self.class.delegate method, :to => :arel
97
+ arel.public_send(method, *args, &block)
98
+ else
99
+ super
100
+ end
101
+ end
102
+ end
103
+
104
+ module ClassMethods # :nodoc:
105
+ def create(klass, *args)
106
+ relation_class_for(klass).new(klass, *args)
107
+ end
108
+
109
+ private
110
+
111
+ def relation_class_for(klass)
112
+ klass.relation_delegate_class(self)
113
+ end
114
+ end
115
+
116
+ def respond_to?(method, include_private = false)
117
+ super || @klass.respond_to?(method, include_private) ||
118
+ array_delegable?(method) ||
119
+ arel.respond_to?(method, include_private)
120
+ end
121
+
122
+ protected
123
+
124
+ def array_delegable?(method)
125
+ Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
126
+ end
127
+
128
+ def method_missing(method, *args, &block)
129
+ if @klass.respond_to?(method)
130
+ scoping { @klass.public_send(method, *args, &block) }
131
+ elsif array_delegable?(method)
132
+ to_a.public_send(method, *args, &block)
133
+ elsif arel.respond_to?(method)
134
+ arel.public_send(method, *args, &block)
135
+ else
136
+ super
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,528 @@
1
+ require 'active_support/deprecation'
2
+ require 'active_support/core_ext/string/filters'
3
+
4
+ module ActiveRecord
5
+ module FinderMethods
6
+ ONE_AS_ONE = '1 AS one'
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. If the primary key
10
+ # is an integer, find by id coerces its arguments using +to_i+.
11
+ #
12
+ # Person.find(1) # returns the object for ID = 1
13
+ # Person.find("1") # returns the object for ID = 1
14
+ # Person.find("31-sarah") # returns the object for ID = 31
15
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
16
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
17
+ # Person.find([1]) # returns an array for the object with ID = 1
18
+ # Person.where("administrator = 1").order("created_on DESC").find(1)
19
+ #
20
+ # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found.
21
+ #
22
+ # 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>
24
+ # option if you want the results are sorted.
25
+ #
26
+ # ==== Find with lock
27
+ #
28
+ # Example for find with a lock: Imagine two concurrent transactions:
29
+ # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
30
+ # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
31
+ # transaction has to wait until the first is finished; we get the
32
+ # expected <tt>person.visits == 4</tt>.
33
+ #
34
+ # Person.transaction do
35
+ # person = Person.lock(true).find(1)
36
+ # person.visits += 1
37
+ # person.save!
38
+ # end
39
+ #
40
+ # ==== Variations of +find+
41
+ #
42
+ # Person.where(name: 'Spartacus', rating: 4)
43
+ # # returns a chainable list (which can be empty).
44
+ #
45
+ # Person.find_by(name: 'Spartacus', rating: 4)
46
+ # # returns the first item or nil.
47
+ #
48
+ # Person.where(name: 'Spartacus', rating: 4).first_or_initialize
49
+ # # returns the first item or returns a new instance (requires you call .save to persist against the database).
50
+ #
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.
53
+ #
54
+ # ==== Alternatives for +find+
55
+ #
56
+ # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
57
+ # # returns a boolean indicating if any record with the given conditions exist.
58
+ #
59
+ # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3")
60
+ # # returns a chainable list of instances with only the mentioned fields.
61
+ #
62
+ # Person.where(name: 'Spartacus', rating: 4).ids
63
+ # # returns an Array of ids, available since Rails 3.2.1.
64
+ #
65
+ # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
66
+ # # returns an Array of the required fields, available since Rails 3.1.
67
+ 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
73
+ end
74
+
75
+ # Finds the first record matching the specified conditions. There
76
+ # is no implied ordering so if order matters, you should specify it
77
+ # yourself.
78
+ #
79
+ # If no record is found, returns <tt>nil</tt>.
80
+ #
81
+ # Post.find_by name: 'Spartacus', rating: 4
82
+ # Post.find_by "published_at < ?", 2.weeks.ago
83
+ def find_by(*args)
84
+ where(*args).take
85
+ rescue RangeError
86
+ nil
87
+ end
88
+
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!
93
+ rescue RangeError
94
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
95
+ end
96
+
97
+ # Gives a record (or N records if a parameter is supplied) without any implied
98
+ # order. The order will depend on the database implementation.
99
+ # If an order is supplied it will be respected.
100
+ #
101
+ # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
102
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
103
+ # Person.where(["name LIKE '%?'", name]).take
104
+ def take(limit = nil)
105
+ limit ? limit(limit).to_a : find_take
106
+ end
107
+
108
+ # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
109
+ # is found. Note that <tt>take!</tt> accepts no arguments.
110
+ def take!
111
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
112
+ end
113
+
114
+ # Find the first record (or first N records if a parameter is supplied).
115
+ # If no order is defined it will order by primary key.
116
+ #
117
+ # Person.first # returns the first object fetched by SELECT * FROM people
118
+ # Person.where(["user_name = ?", user_name]).first
119
+ # Person.where(["user_name = :u", { u: user_name }]).first
120
+ # 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
134
+ #
135
+ def first(limit = nil)
136
+ if limit
137
+ find_nth_with_limit(offset_index, limit)
138
+ else
139
+ find_nth(0, offset_index)
140
+ end
141
+ end
142
+
143
+ # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
144
+ # is found. Note that <tt>first!</tt> accepts no arguments.
145
+ def first!
146
+ find_nth! 0
147
+ end
148
+
149
+ # Find the last record (or last N records if a parameter is supplied).
150
+ # If no order is defined it will order by primary key.
151
+ #
152
+ # Person.last # returns the last object fetched by SELECT * FROM people
153
+ # Person.where(["user_name = ?", user_name]).last
154
+ # Person.order("created_on DESC").offset(5).last
155
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
156
+ #
157
+ # Take note that in that last case, the results are sorted in ascending order:
158
+ #
159
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
160
+ #
161
+ # and not:
162
+ #
163
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
164
+ 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
174
+ end
175
+
176
+ # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
177
+ # is found. Note that <tt>last!</tt> accepts no arguments.
178
+ def last!
179
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
180
+ end
181
+
182
+ # Find the second record.
183
+ # If no order is defined it will order by primary key.
184
+ #
185
+ # Person.second # returns the second object fetched by SELECT * FROM people
186
+ # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
187
+ # Person.where(["user_name = :u", { u: user_name }]).second
188
+ def second
189
+ find_nth(1, offset_index)
190
+ end
191
+
192
+ # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
193
+ # is found.
194
+ def second!
195
+ find_nth! 1
196
+ end
197
+
198
+ # Find the third record.
199
+ # If no order is defined it will order by primary key.
200
+ #
201
+ # Person.third # returns the third object fetched by SELECT * FROM people
202
+ # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
203
+ # Person.where(["user_name = :u", { u: user_name }]).third
204
+ def third
205
+ find_nth(2, offset_index)
206
+ end
207
+
208
+ # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
209
+ # is found.
210
+ def third!
211
+ find_nth! 2
212
+ end
213
+
214
+ # Find the fourth record.
215
+ # If no order is defined it will order by primary key.
216
+ #
217
+ # Person.fourth # returns the fourth object fetched by SELECT * FROM people
218
+ # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
219
+ # Person.where(["user_name = :u", { u: user_name }]).fourth
220
+ def fourth
221
+ find_nth(3, offset_index)
222
+ end
223
+
224
+ # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
225
+ # is found.
226
+ def fourth!
227
+ find_nth! 3
228
+ end
229
+
230
+ # Find the fifth record.
231
+ # If no order is defined it will order by primary key.
232
+ #
233
+ # Person.fifth # returns the fifth object fetched by SELECT * FROM people
234
+ # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
235
+ # Person.where(["user_name = :u", { u: user_name }]).fifth
236
+ def fifth
237
+ find_nth(4, offset_index)
238
+ end
239
+
240
+ # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
241
+ # is found.
242
+ def fifth!
243
+ find_nth! 4
244
+ end
245
+
246
+ # Find the forty-second record. Also known as accessing "the reddit".
247
+ # If no order is defined it will order by primary key.
248
+ #
249
+ # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
250
+ # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
251
+ # Person.where(["user_name = :u", { u: user_name }]).forty_two
252
+ def forty_two
253
+ find_nth(41, offset_index)
254
+ end
255
+
256
+ # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
257
+ # is found.
258
+ def forty_two!
259
+ find_nth! 41
260
+ end
261
+
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:
264
+ #
265
+ # * Integer - Finds the record with this primary key.
266
+ # * String - Finds the record with a primary key corresponding to this
267
+ # string (such as <tt>'5'</tt>).
268
+ # * Array - Finds the record that matches these +find+-style conditions
269
+ # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
270
+ # * Hash - Finds the record that matches these +find+-style conditions
271
+ # (such as <tt>{name: 'David'}</tt>).
272
+ # * +false+ - Returns always +false+.
273
+ # * No args - Returns +false+ if the table is empty, +true+ otherwise.
274
+ #
275
+ # For more information about specifying conditions as a hash or array,
276
+ # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
277
+ #
278
+ # Note: You can't pass in a condition as a string (like <tt>name =
279
+ # 'Jamie'</tt>), since it would be sanitized and then queried against
280
+ # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
281
+ #
282
+ # Person.exists?(5)
283
+ # Person.exists?('5')
284
+ # Person.exists?(['name LIKE ?', "%#{query}%"])
285
+ # Person.exists?(id: [1, 4, 8])
286
+ # Person.exists?(name: 'David')
287
+ # Person.exists?(false)
288
+ # Person.exists?
289
+ def exists?(conditions = :none)
290
+ if Base === conditions
291
+ conditions = conditions.id
292
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
293
+ You are passing an instance of ActiveRecord::Base to `exists?`.
294
+ Please pass the id of the object by calling `.id`
295
+ MSG
296
+ end
297
+
298
+ return false if !conditions
299
+
300
+ relation = apply_join_dependency(self, construct_join_dependency)
301
+ return false if ActiveRecord::NullRelation === relation
302
+
303
+ relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
304
+
305
+ case conditions
306
+ when Array, Hash
307
+ relation = relation.where(conditions)
308
+ else
309
+ unless conditions == :none
310
+ relation = where(primary_key => conditions)
311
+ end
312
+ end
313
+
314
+ connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
315
+ end
316
+
317
+ # This method is called whenever no records are found with either a single
318
+ # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
319
+ #
320
+ # The error message is different depending on whether a single id or
321
+ # multiple ids are provided. If multiple ids are provided, then the number
322
+ # of results obtained should be provided in the +result_size+ argument and
323
+ # the expected number of results should be provided in the +expected_size+
324
+ # argument.
325
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
326
+ conditions = arel.where_sql
327
+ conditions = " [#{conditions}]" if conditions
328
+
329
+ if Array(ids).size == 1
330
+ error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
331
+ else
332
+ error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': "
333
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
334
+ end
335
+
336
+ raise RecordNotFound, error
337
+ end
338
+
339
+ private
340
+
341
+ def offset_index
342
+ offset_value || 0
343
+ end
344
+
345
+ def find_with_associations
346
+ # NOTE: the JoinDependency constructed here needs to know about
347
+ # any joins already present in `self`, so pass them in
348
+ #
349
+ # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
350
+ # incorrect SQL is generated. In that case, the join dependency for
351
+ # SpecialCategorizations is constructed without knowledge of the
352
+ # preexisting join in joins_values to categorizations (by way of
353
+ # the `has_many :through` for categories).
354
+ #
355
+ join_dependency = construct_join_dependency(joins_values)
356
+
357
+ aliases = join_dependency.aliases
358
+ relation = select aliases.columns
359
+ relation = apply_join_dependency(relation, join_dependency)
360
+
361
+ if block_given?
362
+ yield relation
363
+ else
364
+ if ActiveRecord::NullRelation === relation
365
+ []
366
+ else
367
+ arel = relation.arel
368
+ rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
369
+ join_dependency.instantiate(rows, aliases)
370
+ end
371
+ end
372
+ end
373
+
374
+ def construct_join_dependency(joins = [])
375
+ including = eager_load_values + includes_values
376
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
377
+ end
378
+
379
+ def construct_relation_for_association_calculations
380
+ from = arel.froms.first
381
+ if Arel::Table === from
382
+ apply_join_dependency(self, construct_join_dependency)
383
+ else
384
+ # FIXME: as far as I can tell, `from` will always be an Arel::Table.
385
+ # There are no tests that test this branch, but presumably it's
386
+ # possible for `from` to be a list?
387
+ apply_join_dependency(self, construct_join_dependency(from))
388
+ end
389
+ end
390
+
391
+ def apply_join_dependency(relation, join_dependency)
392
+ relation = relation.except(:includes, :eager_load, :preload)
393
+ relation = relation.joins join_dependency
394
+
395
+ if using_limitable_reflections?(join_dependency.reflections)
396
+ relation
397
+ else
398
+ if relation.limit_value
399
+ limited_ids = limited_ids_for(relation)
400
+ limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
401
+ end
402
+ relation.except(:limit, :offset)
403
+ end
404
+ end
405
+
406
+ def limited_ids_for(relation)
407
+ values = @klass.connection.columns_for_distinct(
408
+ "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
409
+
410
+ relation = relation.except(:select).select(values).distinct!
411
+ arel = relation.arel
412
+
413
+ id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
414
+ id_rows.map {|row| row[primary_key]}
415
+ end
416
+
417
+ def using_limitable_reflections?(reflections)
418
+ reflections.none? { |r| r.collection? }
419
+ end
420
+
421
+ protected
422
+
423
+ def find_with_ids(*ids)
424
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
425
+
426
+ expects_array = ids.first.kind_of?(Array)
427
+ return ids.first if expects_array && ids.first.empty?
428
+
429
+ ids = ids.flatten.compact.uniq
430
+
431
+ case ids.size
432
+ when 0
433
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
434
+ when 1
435
+ result = find_one(ids.first)
436
+ expects_array ? [ result ] : result
437
+ else
438
+ find_some(ids)
439
+ end
440
+ rescue RangeError
441
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
442
+ end
443
+
444
+ def find_one(id)
445
+ if ActiveRecord::Base === id
446
+ id = id.id
447
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
448
+ You are passing an instance of ActiveRecord::Base to `find`.
449
+ Please pass the id of the object by calling `.id`
450
+ MSG
451
+ end
452
+
453
+ relation = where(primary_key => id)
454
+ record = relation.take
455
+
456
+ raise_record_not_found_exception!(id, 0, 1) unless record
457
+
458
+ record
459
+ end
460
+
461
+ def find_some(ids)
462
+ result = where(primary_key => ids).to_a
463
+
464
+ expected_size =
465
+ if limit_value && ids.size > limit_value
466
+ limit_value
467
+ else
468
+ ids.size
469
+ end
470
+
471
+ # 11 ids with limit 3, offset 9 should give 2 results.
472
+ if offset_value && (ids.size - offset_value < expected_size)
473
+ expected_size = ids.size - offset_value
474
+ end
475
+
476
+ if result.size == expected_size
477
+ result
478
+ else
479
+ raise_record_not_found_exception!(ids, result.size, expected_size)
480
+ end
481
+ end
482
+
483
+ def find_take
484
+ if loaded?
485
+ @records.first
486
+ else
487
+ @take ||= limit(1).to_a.first
488
+ end
489
+ end
490
+
491
+ def find_nth(index, offset)
492
+ if loaded?
493
+ @records[index]
494
+ else
495
+ offset += index
496
+ @offsets[offset] ||= find_nth_with_limit(offset, 1).first
497
+ end
498
+ end
499
+
500
+ def find_nth!(index)
501
+ find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
502
+ end
503
+
504
+ def find_nth_with_limit(offset, limit)
505
+ relation = if order_values.empty? && primary_key
506
+ order(arel_table[primary_key].asc)
507
+ else
508
+ self
509
+ end
510
+
511
+ relation = relation.offset(offset) unless offset.zero?
512
+ relation.limit(limit).to_a
513
+ end
514
+
515
+ def find_last
516
+ if loaded?
517
+ @records.last
518
+ else
519
+ @last ||=
520
+ if limit_value
521
+ to_a.last
522
+ else
523
+ reverse_order.limit(1).to_a.first
524
+ end
525
+ end
526
+ end
527
+ end
528
+ end