activerecord 3.0.0 → 4.0.0

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

Potentially problematic release.


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

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,125 @@
1
+ require 'thread'
2
+ require 'thread_safe'
3
+
4
+ module ActiveRecord
5
+ module Delegation # :nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ # This module creates compiled delegation methods dynamically at runtime, which makes
9
+ # subsequent calls to that method faster by avoiding method_missing. The delegations
10
+ # may vary depending on the klass of a relation, so we create a subclass of Relation
11
+ # for each different klass, and the delegations are compiled into that subclass only.
12
+
13
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
14
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
15
+ :connection, :columns_hash, :to => :klass
16
+
17
+ module ClassSpecificRelation # :nodoc:
18
+ extend ActiveSupport::Concern
19
+
20
+ included do
21
+ @delegation_mutex = Mutex.new
22
+ end
23
+
24
+ module ClassMethods # :nodoc:
25
+ def name
26
+ superclass.name
27
+ end
28
+
29
+ def delegate_to_scoped_klass(method)
30
+ @delegation_mutex.synchronize do
31
+ return if method_defined?(method)
32
+
33
+ if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
34
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
35
+ def #{method}(*args, &block)
36
+ scoping { @klass.#{method}(*args, &block) }
37
+ end
38
+ RUBY
39
+ else
40
+ define_method method do |*args, &block|
41
+ scoping { @klass.send(method, *args, &block) }
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def delegate(method, opts = {})
48
+ @delegation_mutex.synchronize do
49
+ return if method_defined?(method)
50
+ super
51
+ end
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def method_missing(method, *args, &block)
58
+ if @klass.respond_to?(method)
59
+ self.class.delegate_to_scoped_klass(method)
60
+ scoping { @klass.send(method, *args, &block) }
61
+ elsif Array.method_defined?(method)
62
+ self.class.delegate method, :to => :to_a
63
+ to_a.send(method, *args, &block)
64
+ elsif arel.respond_to?(method)
65
+ self.class.delegate method, :to => :arel
66
+ arel.send(method, *args, &block)
67
+ else
68
+ super
69
+ end
70
+ end
71
+ end
72
+
73
+ module ClassMethods # :nodoc:
74
+ @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2)
75
+
76
+ def new(klass, *args)
77
+ relation = relation_class_for(klass).allocate
78
+ relation.__send__(:initialize, klass, *args)
79
+ relation
80
+ end
81
+
82
+ # This doesn't have to be thread-safe. relation_class_for guarantees that this will only be
83
+ # called exactly once for a given const name.
84
+ def const_missing(name)
85
+ const_set(name, Class.new(self) { include ClassSpecificRelation })
86
+ end
87
+
88
+ private
89
+ # Cache the constants in @@subclasses because looking them up via const_get
90
+ # make instantiation significantly slower.
91
+ def relation_class_for(klass)
92
+ if klass && (klass_name = klass.name)
93
+ my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new }
94
+ # This hash is keyed by klass.name to avoid memory leaks in development mode
95
+ my_cache.compute_if_absent(klass_name) do
96
+ # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name
97
+ const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false)
98
+ end
99
+ else
100
+ ActiveRecord::Relation
101
+ end
102
+ end
103
+ end
104
+
105
+ def respond_to?(method, include_private = false)
106
+ super || Array.method_defined?(method) ||
107
+ @klass.respond_to?(method, include_private) ||
108
+ arel.respond_to?(method, include_private)
109
+ end
110
+
111
+ protected
112
+
113
+ def method_missing(method, *args, &block)
114
+ if @klass.respond_to?(method)
115
+ scoping { @klass.send(method, *args, &block) }
116
+ elsif Array.method_defined?(method)
117
+ to_a.send(method, *args, &block)
118
+ elsif arel.respond_to?(method)
119
+ arel.send(method, *args, &block)
120
+ else
121
+ super
122
+ end
123
+ end
124
+ end
125
+ end
@@ -1,150 +1,137 @@
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
- # ==== Parameters
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".
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+.
49
6
  #
50
- # ==== Examples
51
- #
52
- # # find by id
53
7
  # Person.find(1) # returns the object for ID = 1
8
+ # Person.find("1") # returns the object for ID = 1
54
9
  # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
55
10
  # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
56
11
  # Person.find([1]) # returns an array for the object with ID = 1
57
- # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
12
+ # Person.where("administrator = 1").order("created_on DESC").find(1)
58
13
  #
59
14
  # 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>
15
+ # provide since database rows are unordered. Give an explicit <tt>order</tt>
61
16
  # to ensure the results are sorted.
62
17
  #
63
- # ==== Examples
64
- #
65
- # # find first
66
- # Person.find(:first) # returns the first object fetched by SELECT * FROM people
67
- # Person.find(:first, :conditions => [ "user_name = ?", user_name])
68
- # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
69
- # Person.find(:first, :order => "created_on DESC", :offset => 5)
70
- #
71
- # # find last
72
- # Person.find(:last) # returns the last object fetched by SELECT * FROM people
73
- # Person.find(:last, :conditions => [ "user_name = ?", user_name])
74
- # Person.find(:last, :order => "created_on DESC", :offset => 5)
75
- #
76
- # # find all
77
- # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
78
- # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
79
- # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
80
- # Person.find(:all, :offset => 10, :limit => 10)
81
- # Person.find(:all, :include => [ :account, :friends ])
82
- # Person.find(:all, :group => "category")
18
+ # ==== Find with lock
83
19
  #
84
20
  # Example for find with a lock: Imagine two concurrent transactions:
85
21
  # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
86
- # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
22
+ # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
87
23
  # transaction has to wait until the first is finished; we get the
88
24
  # expected <tt>person.visits == 4</tt>.
89
25
  #
90
26
  # Person.transaction do
91
- # person = Person.find(1, :lock => true)
27
+ # person = Person.lock(true).find(1)
92
28
  # person.visits += 1
93
29
  # person.save!
94
30
  # end
95
31
  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)
32
+ if block_given?
33
+ to_a.find { |*block_args| yield(*block_args) }
102
34
  else
103
- case args.first
104
- when :first, :last, :all
105
- send(args.first)
106
- else
107
- find_with_ids(*args)
108
- end
35
+ find_with_ids(*args)
109
36
  end
110
37
  end
111
38
 
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
- to_a.first(*args)
39
+ # Finds the first record matching the specified conditions. There
40
+ # is no implied ordering so if order matters, you should specify it
41
+ # yourself.
42
+ #
43
+ # If no record is found, returns <tt>nil</tt>.
44
+ #
45
+ # Post.find_by name: 'Spartacus', rating: 4
46
+ # Post.find_by "published_at < ?", 2.weeks.ago
47
+ def find_by(*args)
48
+ where(*args).take
49
+ end
50
+
51
+ # Like <tt>find_by</tt>, except that if no record is found, raises
52
+ # an <tt>ActiveRecord::RecordNotFound</tt> error.
53
+ def find_by!(*args)
54
+ where(*args).take!
55
+ end
56
+
57
+ # Gives a record (or N records if a parameter is supplied) without any implied
58
+ # order. The order will depend on the database implementation.
59
+ # If an order is supplied it will be respected.
60
+ #
61
+ # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
62
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
63
+ # Person.where(["name LIKE '%?'", name]).take
64
+ def take(limit = nil)
65
+ limit ? limit(limit).to_a : find_take
66
+ end
67
+
68
+ # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
69
+ # is found. Note that <tt>take!</tt> accepts no arguments.
70
+ def take!
71
+ take or raise RecordNotFound
72
+ end
73
+
74
+ # Find the first record (or first N records if a parameter is supplied).
75
+ # If no order is defined it will order by primary key.
76
+ #
77
+ # Person.first # returns the first object fetched by SELECT * FROM people
78
+ # Person.where(["user_name = ?", user_name]).first
79
+ # Person.where(["user_name = :u", { u: user_name }]).first
80
+ # Person.order("created_on DESC").offset(5).first
81
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
82
+ def first(limit = nil)
83
+ if limit
84
+ if order_values.empty? && primary_key
85
+ order(arel_table[primary_key].asc).limit(limit).to_a
118
86
  else
119
- apply_finder_options(args.first).first
87
+ limit(limit).to_a
120
88
  end
121
89
  else
122
90
  find_first
123
91
  end
124
92
  end
125
93
 
126
- # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
127
- # same arguments to this method as you can to <tt>find(:last)</tt>.
128
- def last(*args)
129
- if args.any?
130
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
131
- to_a.last(*args)
94
+ # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
95
+ # is found. Note that <tt>first!</tt> accepts no arguments.
96
+ def first!
97
+ first or raise RecordNotFound
98
+ end
99
+
100
+ # Find the last record (or last N records if a parameter is supplied).
101
+ # If no order is defined it will order by primary key.
102
+ #
103
+ # Person.last # returns the last object fetched by SELECT * FROM people
104
+ # Person.where(["user_name = ?", user_name]).last
105
+ # Person.order("created_on DESC").offset(5).last
106
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
107
+ #
108
+ # Take note that in that last case, the results are sorted in ascending order:
109
+ #
110
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
111
+ #
112
+ # and not:
113
+ #
114
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
115
+ def last(limit = nil)
116
+ if limit
117
+ if order_values.empty? && primary_key
118
+ order(arel_table[primary_key].desc).limit(limit).reverse
132
119
  else
133
- apply_finder_options(args.first).last
120
+ to_a.last(limit)
134
121
  end
135
122
  else
136
123
  find_last
137
124
  end
138
125
  end
139
126
 
140
- # A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
141
- # same arguments to this method as you can to <tt>find(:all)</tt>.
142
- def all(*args)
143
- args.any? ? apply_finder_options(args.first).to_a : to_a
127
+ # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
128
+ # is found. Note that <tt>last!</tt> accepts no arguments.
129
+ def last!
130
+ last or raise RecordNotFound
144
131
  end
145
132
 
146
- # Returns true if a record exists in the table that matches the +id+ or
147
- # conditions given, or false otherwise. The argument can take five forms:
133
+ # Returns truthy if a record exists in the table that matches the +id+ or
134
+ # conditions given, or falsy otherwise. The argument can take six forms:
148
135
  #
149
136
  # * Integer - Finds the record with this primary key.
150
137
  # * String - Finds the record with a primary key corresponding to this
@@ -152,8 +139,9 @@ module ActiveRecord
152
139
  # * Array - Finds the record that matches these +find+-style conditions
153
140
  # (such as <tt>['color = ?', 'red']</tt>).
154
141
  # * Hash - Finds the record that matches these +find+-style conditions
155
- # (such as <tt>{:color => 'red'}</tt>).
156
- # * No args - Returns false if the table is empty, true otherwise.
142
+ # (such as <tt>{color: 'red'}</tt>).
143
+ # * +false+ - Returns always +false+.
144
+ # * No args - Returns +false+ if the table is empty, +true+ otherwise.
157
145
  #
158
146
  # For more information about specifying conditions as a Hash or Array,
159
147
  # see the Conditions section in the introduction to ActiveRecord::Base.
@@ -162,50 +150,84 @@ module ActiveRecord
162
150
  # 'Jamie'</tt>), since it would be sanitized and then queried against
163
151
  # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
164
152
  #
165
- # ==== Examples
166
153
  # Person.exists?(5)
167
154
  # Person.exists?('5')
168
- # Person.exists?(:name => "David")
169
155
  # Person.exists?(['name LIKE ?', "%#{query}%"])
156
+ # Person.exists?(name: 'David')
157
+ # Person.exists?(false)
170
158
  # Person.exists?
171
- def exists?(id = nil)
172
- id = id.id if ActiveRecord::Base === id
159
+ def exists?(conditions = :none)
160
+ conditions = conditions.id if Base === conditions
161
+ return false if !conditions
162
+
163
+ join_dependency = construct_join_dependency_for_association_find
164
+ relation = construct_relation_for_association_find(join_dependency)
165
+ relation = relation.except(:select, :order).select("1 AS one").limit(1)
173
166
 
174
- case id
167
+ case conditions
175
168
  when Array, Hash
176
- where(id).exists?
169
+ relation = relation.where(conditions)
170
+ else
171
+ relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
172
+ end
173
+
174
+ connection.select_value(relation, "#{name} Exists", relation.bind_values)
175
+ rescue ThrowResult
176
+ false
177
+ end
178
+
179
+ # This method is called whenever no records are found with either a single
180
+ # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
181
+ #
182
+ # The error message is different depending on whether a single id or
183
+ # multiple ids are provided. If multiple ids are provided, then the number
184
+ # of results obtained should be provided in the +result_size+ argument and
185
+ # the expected number of results should be provided in the +expected_size+
186
+ # argument.
187
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
188
+ conditions = arel.where_sql
189
+ conditions = " [#{conditions}]" if conditions
190
+
191
+ if Array(ids).size == 1
192
+ error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
177
193
  else
178
- relation = select(primary_key).limit(1)
179
- relation = relation.where(primary_key.eq(id)) if id
180
- relation.first ? true : false
194
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
195
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
181
196
  end
197
+
198
+ raise RecordNotFound, error
182
199
  end
183
200
 
184
201
  protected
185
202
 
186
203
  def find_with_associations
187
- including = (@eager_load_values + @includes_values).uniq
188
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
189
- rows = construct_relation_for_association_find(join_dependency).to_a
204
+ join_dependency = construct_join_dependency_for_association_find
205
+ relation = construct_relation_for_association_find(join_dependency)
206
+ rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
190
207
  join_dependency.instantiate(rows)
191
208
  rescue ThrowResult
192
209
  []
193
210
  end
194
211
 
212
+ def construct_join_dependency_for_association_find
213
+ including = (eager_load_values + includes_values).uniq
214
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
215
+ end
216
+
195
217
  def construct_relation_for_association_calculations
196
- including = (@eager_load_values + @includes_values).uniq
197
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.joins(arel))
218
+ including = (eager_load_values + includes_values).uniq
219
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
198
220
  relation = except(:includes, :eager_load, :preload)
199
221
  apply_join_dependency(relation, join_dependency)
200
222
  end
201
223
 
202
224
  def construct_relation_for_association_find(join_dependency)
203
- relation = except(:includes, :eager_load, :preload, :select).select(column_aliases(join_dependency))
225
+ relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
204
226
  apply_join_dependency(relation, join_dependency)
205
227
  end
206
228
 
207
229
  def apply_join_dependency(relation, join_dependency)
208
- for association in join_dependency.join_associations
230
+ join_dependency.join_associations.each do |association|
209
231
  relation = association.join_relation(relation)
210
232
  end
211
233
 
@@ -222,53 +244,18 @@ module ActiveRecord
222
244
  end
223
245
 
224
246
  def construct_limited_ids_condition(relation)
225
- orders = relation.order_values.join(", ")
226
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders)
227
-
228
- ids_array = relation.select(values).collect {|row| row[@klass.primary_key]}
229
- ids_array.empty? ? raise(ThrowResult) : primary_key.in(ids_array)
230
- end
231
-
232
- def find_by_attributes(match, attributes, *args)
233
- conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h}
234
- result = where(conditions).send(match.finder)
235
-
236
- if match.bang? && result.blank?
237
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
238
- else
239
- result
240
- end
241
- end
242
-
243
- def find_or_instantiator_by_attributes(match, attributes, *args)
244
- protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
245
- args.each_with_index do |arg, i|
246
- if arg.is_a?(Hash)
247
- protected_attributes_for_create = args[i].with_indifferent_access
248
- else
249
- unprotected_attributes_for_create[attributes[i]] = args[i]
250
- end
251
- end
247
+ orders = relation.order_values.map { |val| val.presence }.compact
248
+ values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
252
249
 
253
- conditions = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes).symbolize_keys
250
+ relation = relation.dup.select(values)
254
251
 
255
- record = where(conditions).first
252
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
253
+ ids_array = id_rows.map {|row| row[primary_key]}
256
254
 
257
- unless record
258
- record = @klass.new do |r|
259
- r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
260
- r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
261
- end
262
- yield(record) if block_given?
263
- record.save if match.instantiator == :create
264
- end
265
-
266
- record
255
+ ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
267
256
  end
268
257
 
269
258
  def find_with_ids(*ids)
270
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
271
-
272
259
  expects_array = ids.first.kind_of?(Array)
273
260
  return ids.first if expects_array && ids.first.empty?
274
261
 
@@ -288,41 +275,44 @@ module ActiveRecord
288
275
  def find_one(id)
289
276
  id = id.id if ActiveRecord::Base === id
290
277
 
291
- record = where(primary_key.eq(id)).first
278
+ column = columns_hash[primary_key]
279
+ substitute = connection.substitute_at(column, bind_values.length)
280
+ relation = where(table[primary_key].eq(substitute))
281
+ relation.bind_values += [[column, id]]
282
+ record = relation.take
292
283
 
293
- unless record
294
- conditions = arel.wheres.map { |x| x.value }.join(', ')
295
- conditions = " [WHERE #{conditions}]" if conditions.present?
296
- raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}"
297
- end
284
+ raise_record_not_found_exception!(id, 0, 1) unless record
298
285
 
299
286
  record
300
287
  end
301
288
 
302
289
  def find_some(ids)
303
- result = where(primary_key.in(ids)).all
290
+ result = where(table[primary_key].in(ids)).to_a
304
291
 
305
292
  expected_size =
306
- if @limit_value && ids.size > @limit_value
307
- @limit_value
293
+ if limit_value && ids.size > limit_value
294
+ limit_value
308
295
  else
309
296
  ids.size
310
297
  end
311
298
 
312
299
  # 11 ids with limit 3, offset 9 should give 2 results.
313
- if @offset_value && (ids.size - @offset_value < expected_size)
314
- expected_size = ids.size - @offset_value
300
+ if offset_value && (ids.size - offset_value < expected_size)
301
+ expected_size = ids.size - offset_value
315
302
  end
316
303
 
317
304
  if result.size == expected_size
318
305
  result
319
306
  else
320
- conditions = arel.wheres.map { |x| x.value }.join(', ')
321
- conditions = " [WHERE #{conditions}]" if conditions.present?
307
+ raise_record_not_found_exception!(ids, result.size, expected_size)
308
+ end
309
+ end
322
310
 
323
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
324
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
325
- raise RecordNotFound, error
311
+ def find_take
312
+ if loaded?
313
+ @records.first
314
+ else
315
+ @take ||= limit(1).to_a.first
326
316
  end
327
317
  end
328
318
 
@@ -330,7 +320,12 @@ module ActiveRecord
330
320
  if loaded?
331
321
  @records.first
332
322
  else
333
- @first ||= limit(1).to_a[0]
323
+ @first ||=
324
+ if with_default_scope.order_values.empty? && primary_key
325
+ order(arel_table[primary_key].asc).limit(1).to_a.first
326
+ else
327
+ limit(1).to_a.first
328
+ end
334
329
  end
335
330
  end
336
331
 
@@ -338,18 +333,17 @@ module ActiveRecord
338
333
  if loaded?
339
334
  @records.last
340
335
  else
341
- @last ||= reverse_order.limit(1).to_a[0]
336
+ @last ||=
337
+ if offset_value || limit_value
338
+ to_a.last
339
+ else
340
+ reverse_order.limit(1).to_a.first
341
+ end
342
342
  end
343
343
  end
344
344
 
345
- def column_aliases(join_dependency)
346
- join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
347
- "#{connection.quote_table_name join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
348
- end
349
-
350
345
  def using_limitable_reflections?(reflections)
351
346
  reflections.none? { |r| r.collection? }
352
347
  end
353
-
354
348
  end
355
349
  end