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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +35 -44
- data/examples/performance.rb +110 -100
- data/lib/active_record/aggregations.rb +59 -75
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +60 -59
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
- data/lib/active_record/associations/has_many_association.rb +83 -76
- data/lib/active_record/associations/has_many_through_association.rb +147 -66
- data/lib/active_record/associations/has_one_association.rb +67 -108
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +512 -1224
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
- data/lib/active_record/attribute_methods/dirty.rb +51 -28
- data/lib/active_record/attribute_methods/primary_key.rb +94 -22
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +63 -72
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
- data/lib/active_record/attribute_methods/write.rb +39 -13
- data/lib/active_record/attribute_methods.rb +362 -29
- data/lib/active_record/autosave_association.rb +132 -75
- data/lib/active_record/base.rb +83 -1627
- data/lib/active_record/callbacks.rb +69 -47
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +108 -101
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +54 -13
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +703 -785
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +69 -60
- data/lib/active_record/locking/pessimistic.rb +34 -12
- data/lib/active_record/log_subscriber.rb +40 -6
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +614 -216
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +248 -119
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +275 -57
- data/lib/active_record/query_cache.rb +29 -9
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +135 -21
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +17 -5
- data/lib/active_record/railties/databases.rake +249 -359
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +283 -103
- data/lib/active_record/relation/batches.rb +38 -34
- data/lib/active_record/relation/calculations.rb +252 -139
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +182 -188
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +86 -21
- data/lib/active_record/relation/query_methods.rb +917 -134
- data/lib/active_record/relation/spawn_methods.rb +53 -92
- data/lib/active_record/relation.rb +405 -143
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +20 -14
- data/lib/active_record/schema_dumper.rb +55 -46
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +8 -46
- data/lib/active_record/serializers/xml_serializer.rb +21 -68
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +57 -28
- data/lib/active_record/timestamp.rb +49 -18
- data/lib/active_record/transactions.rb +106 -63
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +25 -24
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +123 -83
- data/lib/active_record/validations.rb +29 -29
- data/lib/active_record/version.rb +7 -5
- data/lib/active_record.rb +83 -34
- data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
- data/lib/rails/generators/active_record.rb +4 -8
- metadata +163 -121
- data/CHANGELOG +0 -6023
- data/examples/associations.png +0 -0
- data/lib/active_record/association_preload.rb +0 -403
- data/lib/active_record/associations/association_collection.rb +0 -562
- data/lib/active_record/associations/association_proxy.rb +0 -295
- data/lib/active_record/associations/through_association_scope.rb +0 -154
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
- data/lib/active_record/dynamic_finder_match.rb +0 -53
- data/lib/active_record/dynamic_scope_match.rb +0 -32
- data/lib/active_record/named_scope.rb +0 -138
- data/lib/active_record/observer.rb +0 -140
- data/lib/active_record/session_store.rb +0 -340
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
- 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
|
7
|
-
#
|
8
|
-
#
|
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.
|
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
|
15
|
+
# provide since database rows are unordered. Give an explicit <tt>order</tt>
|
61
16
|
# to ensure the results are sorted.
|
62
17
|
#
|
63
|
-
# ====
|
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>.
|
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
|
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
|
-
|
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
|
-
|
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
|
-
#
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
87
|
+
limit(limit).to_a
|
120
88
|
end
|
121
89
|
else
|
122
90
|
find_first
|
123
91
|
end
|
124
92
|
end
|
125
93
|
|
126
|
-
#
|
127
|
-
#
|
128
|
-
def
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
120
|
+
to_a.last(limit)
|
134
121
|
end
|
135
122
|
else
|
136
123
|
find_last
|
137
124
|
end
|
138
125
|
end
|
139
126
|
|
140
|
-
#
|
141
|
-
#
|
142
|
-
def
|
143
|
-
|
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
|
147
|
-
# conditions given, or
|
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>{:
|
156
|
-
# *
|
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?(
|
172
|
-
|
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
|
167
|
+
case conditions
|
175
168
|
when Array, Hash
|
176
|
-
where(
|
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
|
-
|
179
|
-
|
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
|
-
|
188
|
-
|
189
|
-
rows =
|
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 = (
|
197
|
-
join_dependency = ActiveRecord::Associations::
|
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(
|
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
|
-
|
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.
|
226
|
-
values = @klass.connection.distinct("#{
|
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
|
-
|
250
|
+
relation = relation.dup.select(values)
|
254
251
|
|
255
|
-
|
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
|
-
|
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
|
-
|
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)).
|
290
|
+
result = where(table[primary_key].in(ids)).to_a
|
304
291
|
|
305
292
|
expected_size =
|
306
|
-
if
|
307
|
-
|
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
|
314
|
-
expected_size = ids.size -
|
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
|
-
|
321
|
-
|
307
|
+
raise_record_not_found_exception!(ids, result.size, expected_size)
|
308
|
+
end
|
309
|
+
end
|
322
310
|
|
323
|
-
|
324
|
-
|
325
|
-
|
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 ||=
|
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 ||=
|
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
|