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