activerecord 3.2.22.5 → 4.0.0.beta1
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 +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- 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 +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- 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 +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- 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/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- 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 +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -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/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- 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
@@ -0,0 +1,133 @@
|
|
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
|
+
else
|
34
|
+
other.send("#{k}!", v)
|
35
|
+
end
|
36
|
+
}
|
37
|
+
other
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Merger # :nodoc:
|
42
|
+
attr_reader :relation, :values
|
43
|
+
|
44
|
+
def initialize(relation, other)
|
45
|
+
if other.default_scoped? && other.klass != relation.klass
|
46
|
+
other = other.with_default_scope
|
47
|
+
end
|
48
|
+
|
49
|
+
@relation = relation
|
50
|
+
@values = other.values
|
51
|
+
end
|
52
|
+
|
53
|
+
NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
|
54
|
+
Relation::MULTI_VALUE_METHODS -
|
55
|
+
[:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
|
56
|
+
|
57
|
+
def normal_values
|
58
|
+
NORMAL_VALUES
|
59
|
+
end
|
60
|
+
|
61
|
+
def merge
|
62
|
+
normal_values.each do |name|
|
63
|
+
value = values[name]
|
64
|
+
relation.send("#{name}!", *value) unless value.blank?
|
65
|
+
end
|
66
|
+
|
67
|
+
merge_multi_values
|
68
|
+
merge_single_values
|
69
|
+
|
70
|
+
relation
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def merge_multi_values
|
76
|
+
relation.where_values = merged_wheres
|
77
|
+
relation.bind_values = merged_binds
|
78
|
+
|
79
|
+
if values[:reordering]
|
80
|
+
# override any order specified in the original relation
|
81
|
+
relation.reorder! values[:order]
|
82
|
+
elsif values[:order]
|
83
|
+
# merge in order_values from r
|
84
|
+
relation.order! values[:order]
|
85
|
+
end
|
86
|
+
|
87
|
+
relation.extend(*values[:extending]) unless values[:extending].blank?
|
88
|
+
end
|
89
|
+
|
90
|
+
def merge_single_values
|
91
|
+
relation.from_value = values[:from] unless relation.from_value
|
92
|
+
relation.lock_value = values[:lock] unless relation.lock_value
|
93
|
+
relation.reverse_order_value = values[:reverse_order]
|
94
|
+
|
95
|
+
unless values[:create_with].blank?
|
96
|
+
relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def merged_binds
|
101
|
+
if values[:bind]
|
102
|
+
(relation.bind_values + values[:bind]).uniq(&:first)
|
103
|
+
else
|
104
|
+
relation.bind_values
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def merged_wheres
|
109
|
+
values[:where] ||= []
|
110
|
+
|
111
|
+
if values[:where].empty? || relation.where_values.empty?
|
112
|
+
relation.where_values + values[:where]
|
113
|
+
else
|
114
|
+
# Remove equalities from the existing relation with a LHS which is
|
115
|
+
# present in the relation being merged in.
|
116
|
+
|
117
|
+
seen = Set.new
|
118
|
+
values[:where].each { |w|
|
119
|
+
if w.respond_to?(:operator) && w.operator == :==
|
120
|
+
seen << w.left
|
121
|
+
end
|
122
|
+
}
|
123
|
+
|
124
|
+
relation.where_values.reject { |w|
|
125
|
+
w.respond_to?(:operator) &&
|
126
|
+
w.operator == :== &&
|
127
|
+
seen.include?(w.left)
|
128
|
+
} + values[:where]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -1,63 +1,111 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class PredicateBuilder # :nodoc:
|
3
|
-
def self.build_from_hash(
|
4
|
-
|
5
|
-
table = default_table
|
3
|
+
def self.build_from_hash(klass, attributes, default_table)
|
4
|
+
queries = []
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
attributes.each do |column, value|
|
7
|
+
table = default_table
|
9
8
|
|
9
|
+
if value.is_a?(Hash)
|
10
10
|
if value.empty?
|
11
|
-
'1
|
11
|
+
queries << '1=0'
|
12
12
|
else
|
13
|
-
|
13
|
+
table = Arel::Table.new(column, default_table.engine)
|
14
|
+
association = klass.reflect_on_association(column.to_sym)
|
15
|
+
|
16
|
+
value.each do |k, v|
|
17
|
+
queries.concat expand(association && association.klass, table, k, v)
|
18
|
+
end
|
14
19
|
end
|
15
20
|
else
|
16
21
|
column = column.to_s
|
17
22
|
|
18
|
-
if
|
23
|
+
if column.include?('.')
|
19
24
|
table_name, column = column.split('.', 2)
|
20
|
-
table = Arel::Table.new(table_name, engine)
|
25
|
+
table = Arel::Table.new(table_name, default_table.engine)
|
21
26
|
end
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
28
|
+
queries.concat expand(klass, table, column, value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
queries
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.expand(klass, table, column, value)
|
36
|
+
queries = []
|
37
|
+
|
38
|
+
# Find the foreign key when using queries such as:
|
39
|
+
# Post.where(author: author)
|
40
|
+
#
|
41
|
+
# For polymorphic relationships, find the foreign key and type:
|
42
|
+
# PriceEstimate.where(estimate_of: treasure)
|
43
|
+
if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym)
|
44
|
+
if reflection.polymorphic?
|
45
|
+
queries << build(table[reflection.foreign_type], value.class.base_class)
|
46
|
+
end
|
47
|
+
|
48
|
+
column = reflection.foreign_key
|
49
|
+
end
|
50
|
+
|
51
|
+
queries << build(table[column.to_sym], value)
|
52
|
+
queries
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.references(attributes)
|
56
|
+
attributes.map do |key, value|
|
57
|
+
if value.is_a?(Hash)
|
58
|
+
key
|
59
|
+
else
|
60
|
+
key = key.to_s
|
61
|
+
key.split('.').first if key.include?('.')
|
62
|
+
end
|
63
|
+
end.compact
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def self.build(attribute, value)
|
68
|
+
case value
|
69
|
+
when Array
|
70
|
+
values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x}
|
71
|
+
ranges, values = values.partition {|v| v.is_a?(Range)}
|
72
|
+
|
73
|
+
values_predicate = if values.include?(nil)
|
74
|
+
values = values.compact
|
75
|
+
|
76
|
+
case values.length
|
77
|
+
when 0
|
78
|
+
attribute.eq(nil)
|
79
|
+
when 1
|
80
|
+
attribute.eq(values.first).or(attribute.eq(nil))
|
42
81
|
else
|
43
|
-
|
82
|
+
attribute.in(values).or(attribute.eq(nil))
|
44
83
|
end
|
45
|
-
|
46
|
-
array_predicates.inject {|composite, predicate| composite.or(predicate)}
|
47
|
-
when Range, Arel::Relation
|
48
|
-
attribute.in(value)
|
49
|
-
when ActiveRecord::Base
|
50
|
-
attribute.eq(value.id)
|
51
|
-
when Class
|
52
|
-
# FIXME: I think we need to deprecate this behavior
|
53
|
-
attribute.eq(value.name)
|
54
84
|
else
|
55
|
-
attribute.
|
85
|
+
attribute.in(values)
|
56
86
|
end
|
87
|
+
|
88
|
+
array_predicates = ranges.map { |range| attribute.in(range) }
|
89
|
+
array_predicates << values_predicate
|
90
|
+
array_predicates.inject { |composite, predicate| composite.or(predicate) }
|
91
|
+
when ActiveRecord::Relation
|
92
|
+
value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
|
93
|
+
attribute.in(value.arel.ast)
|
94
|
+
when Range
|
95
|
+
attribute.in(value)
|
96
|
+
when ActiveRecord::Base
|
97
|
+
attribute.eq(value.id)
|
98
|
+
when Class
|
99
|
+
# FIXME: I think we need to deprecate this behavior
|
100
|
+
attribute.eq(value.name)
|
101
|
+
when Integer, ActiveSupport::Duration
|
102
|
+
# Arel treats integers as literals, but they should be quoted when compared with strings
|
103
|
+
table = attribute.relation
|
104
|
+
column = table.engine.connection.schema_cache.columns_hash(table.name)[attribute.name.to_s]
|
105
|
+
attribute.eq(Arel::Nodes::SqlLiteral.new(table.engine.connection.quote(value, column)))
|
106
|
+
else
|
107
|
+
attribute.eq(value)
|
57
108
|
end
|
58
109
|
end
|
59
|
-
|
60
|
-
predicates.flatten
|
61
|
-
end
|
62
110
|
end
|
63
111
|
end
|
@@ -1,48 +1,183 @@
|
|
1
1
|
require 'active_support/core_ext/array/wrap'
|
2
|
-
require 'active_support/core_ext/object/blank'
|
3
2
|
|
4
3
|
module ActiveRecord
|
5
4
|
module QueryMethods
|
6
5
|
extend ActiveSupport::Concern
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
|
8
|
+
# In this case, #where must be chained with either #not, #like, or #not_like to return a new relation.
|
9
|
+
class WhereChain
|
10
|
+
def initialize(scope)
|
11
|
+
@scope = scope
|
12
|
+
end
|
14
13
|
|
14
|
+
# Returns a new relation expressing WHERE + NOT condition according to
|
15
|
+
# the conditions in the arguments.
|
16
|
+
#
|
17
|
+
# +not+ accepts conditions as a string, array, or hash. See #where for
|
18
|
+
# more details on each format.
|
19
|
+
#
|
20
|
+
# User.where.not("name = 'Jon'")
|
21
|
+
# # SELECT * FROM users WHERE NOT (name = 'Jon')
|
22
|
+
#
|
23
|
+
# User.where.not(["name = ?", "Jon"])
|
24
|
+
# # SELECT * FROM users WHERE NOT (name = 'Jon')
|
25
|
+
#
|
26
|
+
# User.where.not(name: "Jon")
|
27
|
+
# # SELECT * FROM users WHERE name != 'Jon'
|
28
|
+
#
|
29
|
+
# User.where.not(name: nil)
|
30
|
+
# # SELECT * FROM users WHERE name IS NOT NULL
|
31
|
+
#
|
32
|
+
# User.where.not(name: %w(Ko1 Nobu))
|
33
|
+
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
34
|
+
#
|
35
|
+
# User.where.not(name: "Jon", role: "admin")
|
36
|
+
# # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
|
37
|
+
#
|
38
|
+
def not(opts, *rest)
|
39
|
+
where_value = @scope.send(:build_where, opts, rest).map do |rel|
|
40
|
+
case rel
|
41
|
+
when Arel::Nodes::In
|
42
|
+
Arel::Nodes::NotIn.new(rel.left, rel.right)
|
43
|
+
when Arel::Nodes::Equality
|
44
|
+
Arel::Nodes::NotEqual.new(rel.left, rel.right)
|
45
|
+
when String
|
46
|
+
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
|
47
|
+
else
|
48
|
+
Arel::Nodes::Not.new(rel)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@scope.where_values += where_value
|
52
|
+
@scope
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Relation::MULTI_VALUE_METHODS.each do |name|
|
57
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
58
|
+
def #{name}_values # def select_values
|
59
|
+
@values[:#{name}] || [] # @values[:select] || []
|
60
|
+
end # end
|
61
|
+
#
|
62
|
+
def #{name}_values=(values) # def select_values=(values)
|
63
|
+
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
|
64
|
+
@values[:#{name}] = values # @values[:select] = values
|
65
|
+
end # end
|
66
|
+
CODE
|
67
|
+
end
|
68
|
+
|
69
|
+
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
|
70
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
71
|
+
def #{name}_value # def readonly_value
|
72
|
+
@values[:#{name}] # @values[:readonly]
|
73
|
+
end # end
|
74
|
+
CODE
|
75
|
+
end
|
76
|
+
|
77
|
+
Relation::SINGLE_VALUE_METHODS.each do |name|
|
78
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
79
|
+
def #{name}_value=(value) # def readonly_value=(value)
|
80
|
+
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
|
81
|
+
@values[:#{name}] = value # @values[:readonly] = value
|
82
|
+
end # end
|
83
|
+
CODE
|
84
|
+
end
|
85
|
+
|
86
|
+
def create_with_value # :nodoc:
|
87
|
+
@values[:create_with] || {}
|
88
|
+
end
|
89
|
+
|
90
|
+
alias extensions extending_values
|
91
|
+
|
92
|
+
# Specify relationships to be included in the result set. For
|
93
|
+
# example:
|
94
|
+
#
|
95
|
+
# users = User.includes(:address)
|
96
|
+
# users.each do |user|
|
97
|
+
# user.address.city
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# allows you to access the +address+ attribute of the +User+ model without
|
101
|
+
# firing an additional query. This will often result in a
|
102
|
+
# performance improvement over a simple +join+.
|
103
|
+
#
|
104
|
+
# === conditions
|
105
|
+
#
|
106
|
+
# If you want to add conditions to your included models you'll have
|
107
|
+
# to explicitly reference them. For example:
|
108
|
+
#
|
109
|
+
# User.includes(:posts).where('posts.name = ?', 'example')
|
110
|
+
#
|
111
|
+
# Will throw an error, but this will work:
|
112
|
+
#
|
113
|
+
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
15
114
|
def includes(*args)
|
16
|
-
|
115
|
+
check_if_method_has_arguments!("includes", args)
|
116
|
+
spawn.includes!(*args)
|
117
|
+
end
|
17
118
|
|
18
|
-
|
119
|
+
def includes!(*args) # :nodoc:
|
120
|
+
args.reject! {|a| a.blank? }
|
19
121
|
|
20
|
-
|
21
|
-
|
22
|
-
relation
|
122
|
+
self.includes_values = (includes_values + args).flatten.uniq
|
123
|
+
self
|
23
124
|
end
|
24
125
|
|
126
|
+
# Forces eager loading by performing a LEFT OUTER JOIN on +args+:
|
127
|
+
#
|
128
|
+
# User.eager_load(:posts)
|
129
|
+
# => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
|
130
|
+
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
|
131
|
+
# "users"."id"
|
25
132
|
def eager_load(*args)
|
26
|
-
|
133
|
+
check_if_method_has_arguments!("eager_load", args)
|
134
|
+
spawn.eager_load!(*args)
|
135
|
+
end
|
27
136
|
|
28
|
-
|
29
|
-
|
30
|
-
|
137
|
+
def eager_load!(*args) # :nodoc:
|
138
|
+
self.eager_load_values += args
|
139
|
+
self
|
31
140
|
end
|
32
141
|
|
142
|
+
# Allows preloading of +args+, in the same way that +includes+ does:
|
143
|
+
#
|
144
|
+
# User.preload(:posts)
|
145
|
+
# => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
|
33
146
|
def preload(*args)
|
34
|
-
|
147
|
+
check_if_method_has_arguments!("preload", args)
|
148
|
+
spawn.preload!(*args)
|
149
|
+
end
|
150
|
+
|
151
|
+
def preload!(*args) # :nodoc:
|
152
|
+
self.preload_values += args
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
156
|
+
# Used to indicate that an association is referenced by an SQL string, and should
|
157
|
+
# therefore be JOINed in any query rather than loaded separately.
|
158
|
+
#
|
159
|
+
# User.includes(:posts).where("posts.name = 'foo'")
|
160
|
+
# # => Doesn't JOIN the posts table, resulting in an error.
|
161
|
+
#
|
162
|
+
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
|
163
|
+
# # => Query now knows the string references posts, so adds a JOIN
|
164
|
+
def references(*args)
|
165
|
+
check_if_method_has_arguments!("references", args)
|
166
|
+
spawn.references!(*args)
|
167
|
+
end
|
168
|
+
|
169
|
+
def references!(*args) # :nodoc:
|
170
|
+
args.flatten!
|
35
171
|
|
36
|
-
|
37
|
-
|
38
|
-
relation
|
172
|
+
self.references_values = (references_values + args.map!(&:to_s)).uniq
|
173
|
+
self
|
39
174
|
end
|
40
175
|
|
41
176
|
# Works in two unique ways.
|
42
177
|
#
|
43
178
|
# First: takes a block so it can be used just like Array#select.
|
44
179
|
#
|
45
|
-
# Model.
|
180
|
+
# Model.all.select { |m| m.field == value }
|
46
181
|
#
|
47
182
|
# This will build an array of objects from the database for the scope,
|
48
183
|
# converting them into an array and iterating through them using Array#select.
|
@@ -50,8 +185,8 @@ module ActiveRecord
|
|
50
185
|
# Second: Modifies the SELECT statement for the query so that only certain
|
51
186
|
# fields are retrieved:
|
52
187
|
#
|
53
|
-
#
|
54
|
-
# => [#<Model field:value>]
|
188
|
+
# Model.select(:field)
|
189
|
+
# # => [#<Model field:value>]
|
55
190
|
#
|
56
191
|
# Although in the above example it looks as though this method returns an
|
57
192
|
# array, it actually returns a relation object and can have other query
|
@@ -59,38 +194,99 @@ module ActiveRecord
|
|
59
194
|
#
|
60
195
|
# The argument to the method can also be an array of fields.
|
61
196
|
#
|
62
|
-
#
|
63
|
-
# => [#<Model field: "value", other_field: "value", and_one_more: "value">]
|
197
|
+
# Model.select(:field, :other_field, :and_one_more)
|
198
|
+
# # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
|
64
199
|
#
|
65
|
-
#
|
66
|
-
# will raise a ActiveModel::MissingAttributeError when the getter method for that attribute is used:
|
200
|
+
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
67
201
|
#
|
68
|
-
#
|
69
|
-
# =>
|
70
|
-
|
202
|
+
# Model.select('field AS field_one', 'other_field AS field_two')
|
203
|
+
# # => [#<Model field: "value", other_field: "value">]
|
204
|
+
#
|
205
|
+
# If an alias was specified, it will be accessible from the resulting objects:
|
206
|
+
#
|
207
|
+
# Model.select('field AS field_one').first.field_one
|
208
|
+
# # => "value"
|
209
|
+
#
|
210
|
+
# Accessing attributes of an object that do not have fields retrieved by a select
|
211
|
+
# will throw <tt>ActiveModel::MissingAttributeError</tt>:
|
212
|
+
#
|
213
|
+
# Model.select(:field).first.other_field
|
214
|
+
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
|
215
|
+
def select(*fields)
|
71
216
|
if block_given?
|
72
|
-
to_a.select {|*block_args|
|
217
|
+
to_a.select { |*block_args| yield(*block_args) }
|
73
218
|
else
|
74
|
-
|
75
|
-
|
76
|
-
relation
|
219
|
+
raise ArgumentError, 'Call this with at least one field' if fields.empty?
|
220
|
+
spawn.select!(*fields)
|
77
221
|
end
|
78
222
|
end
|
79
223
|
|
224
|
+
def select!(*fields) # :nodoc:
|
225
|
+
self.select_values += fields.flatten
|
226
|
+
self
|
227
|
+
end
|
228
|
+
|
229
|
+
# Allows to specify a group attribute:
|
230
|
+
#
|
231
|
+
# User.group(:name)
|
232
|
+
# => SELECT "users".* FROM "users" GROUP BY name
|
233
|
+
#
|
234
|
+
# Returns an array with distinct records based on the +group+ attribute:
|
235
|
+
#
|
236
|
+
# User.select([:id, :name])
|
237
|
+
# => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
|
238
|
+
#
|
239
|
+
# User.group(:name)
|
240
|
+
# => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
|
241
|
+
#
|
242
|
+
# User.group('name AS grouped_name, age')
|
243
|
+
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
|
80
244
|
def group(*args)
|
81
|
-
|
245
|
+
check_if_method_has_arguments!("group", args)
|
246
|
+
spawn.group!(*args)
|
247
|
+
end
|
82
248
|
|
83
|
-
|
84
|
-
|
85
|
-
|
249
|
+
def group!(*args) # :nodoc:
|
250
|
+
args.flatten!
|
251
|
+
|
252
|
+
self.group_values += args
|
253
|
+
self
|
86
254
|
end
|
87
255
|
|
256
|
+
# Allows to specify an order attribute:
|
257
|
+
#
|
258
|
+
# User.order('name')
|
259
|
+
# => SELECT "users".* FROM "users" ORDER BY name
|
260
|
+
#
|
261
|
+
# User.order('name DESC')
|
262
|
+
# => SELECT "users".* FROM "users" ORDER BY name DESC
|
263
|
+
#
|
264
|
+
# User.order('name DESC, email')
|
265
|
+
# => SELECT "users".* FROM "users" ORDER BY name DESC, email
|
266
|
+
#
|
267
|
+
# User.order(:name)
|
268
|
+
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
|
269
|
+
#
|
270
|
+
# User.order(email: :desc)
|
271
|
+
# => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
|
272
|
+
#
|
273
|
+
# User.order(:name, email: :desc)
|
274
|
+
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
|
88
275
|
def order(*args)
|
89
|
-
|
276
|
+
check_if_method_has_arguments!("order", args)
|
277
|
+
spawn.order!(*args)
|
278
|
+
end
|
279
|
+
|
280
|
+
def order!(*args) # :nodoc:
|
281
|
+
args.flatten!
|
282
|
+
validate_order_args args
|
283
|
+
|
284
|
+
references = args.reject { |arg| Arel::Node === arg }
|
285
|
+
references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
|
286
|
+
references!(references) if references.any?
|
90
287
|
|
91
|
-
|
92
|
-
|
93
|
-
relation
|
288
|
+
self.order_values = args + self.order_values
|
289
|
+
self
|
94
290
|
end
|
95
291
|
|
96
292
|
# Replaces any existing order defined on the relation with the specified order.
|
@@ -101,91 +297,346 @@ module ActiveRecord
|
|
101
297
|
#
|
102
298
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
103
299
|
#
|
104
|
-
# generates a query with 'ORDER BY
|
105
|
-
#
|
300
|
+
# generates a query with 'ORDER BY name ASC, id ASC'.
|
106
301
|
def reorder(*args)
|
107
|
-
|
302
|
+
check_if_method_has_arguments!("reorder", args)
|
303
|
+
spawn.reorder!(*args)
|
304
|
+
end
|
305
|
+
|
306
|
+
def reorder!(*args) # :nodoc:
|
307
|
+
args.flatten!
|
308
|
+
validate_order_args args
|
108
309
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
relation
|
310
|
+
self.reordering_value = true
|
311
|
+
self.order_values = args
|
312
|
+
self
|
113
313
|
end
|
114
314
|
|
315
|
+
# Performs a joins on +args+:
|
316
|
+
#
|
317
|
+
# User.joins(:posts)
|
318
|
+
# => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
319
|
+
#
|
320
|
+
# You can use strings in order to customize your joins:
|
321
|
+
#
|
322
|
+
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
|
323
|
+
# => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
|
115
324
|
def joins(*args)
|
116
|
-
|
325
|
+
check_if_method_has_arguments!("joins", args)
|
326
|
+
spawn.joins!(*args.compact.flatten)
|
327
|
+
end
|
117
328
|
|
118
|
-
|
329
|
+
def joins!(*args) # :nodoc:
|
330
|
+
self.joins_values += args
|
331
|
+
self
|
332
|
+
end
|
119
333
|
|
120
|
-
|
121
|
-
|
334
|
+
def bind(value)
|
335
|
+
spawn.bind!(value)
|
336
|
+
end
|
122
337
|
|
123
|
-
|
338
|
+
def bind!(value) # :nodoc:
|
339
|
+
self.bind_values += [value]
|
340
|
+
self
|
124
341
|
end
|
125
342
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
343
|
+
# Returns a new relation, which is the result of filtering the current relation
|
344
|
+
# according to the conditions in the arguments.
|
345
|
+
#
|
346
|
+
# #where accepts conditions in one of several formats. In the examples below, the resulting
|
347
|
+
# SQL is given as an illustration; the actual query generated may be different depending
|
348
|
+
# on the database adapter.
|
349
|
+
#
|
350
|
+
# === string
|
351
|
+
#
|
352
|
+
# A single string, without additional arguments, is passed to the query
|
353
|
+
# constructor as a SQL fragment, and used in the where clause of the query.
|
354
|
+
#
|
355
|
+
# Client.where("orders_count = '2'")
|
356
|
+
# # SELECT * from clients where orders_count = '2';
|
357
|
+
#
|
358
|
+
# Note that building your own string from user input may expose your application
|
359
|
+
# to injection attacks if not done properly. As an alternative, it is recommended
|
360
|
+
# to use one of the following methods.
|
361
|
+
#
|
362
|
+
# === array
|
363
|
+
#
|
364
|
+
# If an array is passed, then the first element of the array is treated as a template, and
|
365
|
+
# the remaining elements are inserted into the template to generate the condition.
|
366
|
+
# Active Record takes care of building the query to avoid injection attacks, and will
|
367
|
+
# convert from the ruby type to the database type where needed. Elements are inserted
|
368
|
+
# into the string in the order in which they appear.
|
369
|
+
#
|
370
|
+
# User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
|
371
|
+
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
|
372
|
+
#
|
373
|
+
# Alternatively, you can use named placeholders in the template, and pass a hash as the
|
374
|
+
# second element of the array. The names in the template are replaced with the corresponding
|
375
|
+
# values from the hash.
|
376
|
+
#
|
377
|
+
# User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
|
378
|
+
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
|
379
|
+
#
|
380
|
+
# This can make for more readable code in complex queries.
|
381
|
+
#
|
382
|
+
# Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
|
383
|
+
# than the previous methods; you are responsible for ensuring that the values in the template
|
384
|
+
# are properly quoted. The values are passed to the connector for quoting, but the caller
|
385
|
+
# is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
|
386
|
+
# the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
|
387
|
+
#
|
388
|
+
# User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
|
389
|
+
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
|
390
|
+
#
|
391
|
+
# If #where is called with multiple arguments, these are treated as if they were passed as
|
392
|
+
# the elements of a single array.
|
393
|
+
#
|
394
|
+
# User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
|
395
|
+
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
|
396
|
+
#
|
397
|
+
# When using strings to specify conditions, you can use any operator available from
|
398
|
+
# the database. While this provides the most flexibility, you can also unintentionally introduce
|
399
|
+
# dependencies on the underlying database. If your code is intended for general consumption,
|
400
|
+
# test with multiple database backends.
|
401
|
+
#
|
402
|
+
# === hash
|
403
|
+
#
|
404
|
+
# #where will also accept a hash condition, in which the keys are fields and the values
|
405
|
+
# are values to be searched for.
|
406
|
+
#
|
407
|
+
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
|
408
|
+
#
|
409
|
+
# User.where({ name: "Joe", email: "joe@example.com" })
|
410
|
+
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
|
411
|
+
#
|
412
|
+
# User.where({ name: ["Alice", "Bob"]})
|
413
|
+
# # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
|
414
|
+
#
|
415
|
+
# User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
|
416
|
+
# # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
|
417
|
+
#
|
418
|
+
# In the case of a belongs_to relationship, an association key can be used
|
419
|
+
# to specify the model if an ActiveRecord object is used as the value.
|
420
|
+
#
|
421
|
+
# author = Author.find(1)
|
422
|
+
#
|
423
|
+
# # The following queries will be equivalent:
|
424
|
+
# Post.where(author: author)
|
425
|
+
# Post.where(author_id: author)
|
426
|
+
#
|
427
|
+
# This also works with polymorphic belongs_to relationships:
|
428
|
+
#
|
429
|
+
# treasure = Treasure.create(name: 'gold coins')
|
430
|
+
# treasure.price_estimates << PriceEstimate.create(price: 125)
|
431
|
+
#
|
432
|
+
# # The following queries will be equivalent:
|
433
|
+
# PriceEstimate.where(estimate_of: treasure)
|
434
|
+
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
435
|
+
#
|
436
|
+
# === Joins
|
437
|
+
#
|
438
|
+
# If the relation is the result of a join, you may create a condition which uses any of the
|
439
|
+
# tables in the join. For string and array conditions, use the table name in the condition.
|
440
|
+
#
|
441
|
+
# User.joins(:posts).where("posts.created_at < ?", Time.now)
|
442
|
+
#
|
443
|
+
# For hash conditions, you can either use the table name in the key, or use a sub-hash.
|
444
|
+
#
|
445
|
+
# User.joins(:posts).where({ "posts.published" => true })
|
446
|
+
# User.joins(:posts).where({ posts: { published: true } })
|
447
|
+
#
|
448
|
+
# === no argument
|
449
|
+
#
|
450
|
+
# If no argument is passed, #where returns a new instance of WhereChain, that
|
451
|
+
# can be chained with #not to return a new relation that negates the where clause.
|
452
|
+
#
|
453
|
+
# User.where.not(name: "Jon")
|
454
|
+
# # SELECT * FROM users WHERE name != 'Jon'
|
455
|
+
#
|
456
|
+
# See WhereChain for more details on #not.
|
457
|
+
#
|
458
|
+
# === blank condition
|
459
|
+
#
|
460
|
+
# If the condition is any blank-ish object, then #where is a no-op and returns
|
461
|
+
# the current relation.
|
462
|
+
def where(opts = :chain, *rest)
|
463
|
+
if opts == :chain
|
464
|
+
WhereChain.new(spawn)
|
465
|
+
elsif opts.blank?
|
466
|
+
self
|
467
|
+
else
|
468
|
+
spawn.where!(opts, *rest)
|
469
|
+
end
|
130
470
|
end
|
131
471
|
|
132
|
-
def where(opts, *rest)
|
133
|
-
|
472
|
+
def where!(opts = :chain, *rest) # :nodoc:
|
473
|
+
if opts == :chain
|
474
|
+
WhereChain.new(self)
|
475
|
+
else
|
476
|
+
references!(PredicateBuilder.references(opts)) if Hash === opts
|
134
477
|
|
135
|
-
|
136
|
-
|
137
|
-
|
478
|
+
self.where_values += build_where(opts, rest)
|
479
|
+
self
|
480
|
+
end
|
138
481
|
end
|
139
482
|
|
483
|
+
# Allows to specify a HAVING clause. Note that you can't use HAVING
|
484
|
+
# without also specifying a GROUP clause.
|
485
|
+
#
|
486
|
+
# Order.having('SUM(price) > 30').group('user_id')
|
140
487
|
def having(opts, *rest)
|
141
|
-
|
488
|
+
opts.blank? ? self : spawn.having!(opts, *rest)
|
489
|
+
spawn.having!(opts, *rest)
|
490
|
+
end
|
491
|
+
|
492
|
+
def having!(opts, *rest) # :nodoc:
|
493
|
+
references!(PredicateBuilder.references(opts)) if Hash === opts
|
142
494
|
|
143
|
-
|
144
|
-
|
145
|
-
relation
|
495
|
+
self.having_values += build_where(opts, rest)
|
496
|
+
self
|
146
497
|
end
|
147
498
|
|
499
|
+
# Specifies a limit for the number of records to retrieve.
|
500
|
+
#
|
501
|
+
# User.limit(10) # generated SQL has 'LIMIT 10'
|
502
|
+
#
|
503
|
+
# User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
|
148
504
|
def limit(value)
|
149
|
-
|
150
|
-
|
151
|
-
|
505
|
+
spawn.limit!(value)
|
506
|
+
end
|
507
|
+
|
508
|
+
def limit!(value) # :nodoc:
|
509
|
+
self.limit_value = value
|
510
|
+
self
|
152
511
|
end
|
153
512
|
|
513
|
+
# Specifies the number of rows to skip before returning rows.
|
514
|
+
#
|
515
|
+
# User.offset(10) # generated SQL has "OFFSET 10"
|
516
|
+
#
|
517
|
+
# Should be used with order.
|
518
|
+
#
|
519
|
+
# User.offset(10).order("name ASC")
|
154
520
|
def offset(value)
|
155
|
-
|
156
|
-
relation.offset_value = value
|
157
|
-
relation
|
521
|
+
spawn.offset!(value)
|
158
522
|
end
|
159
523
|
|
524
|
+
def offset!(value) # :nodoc:
|
525
|
+
self.offset_value = value
|
526
|
+
self
|
527
|
+
end
|
528
|
+
|
529
|
+
# Specifies locking settings (default to +true+). For more information
|
530
|
+
# on locking, please see +ActiveRecord::Locking+.
|
160
531
|
def lock(locks = true)
|
161
|
-
|
532
|
+
spawn.lock!(locks)
|
533
|
+
end
|
162
534
|
|
535
|
+
def lock!(locks = true) # :nodoc:
|
163
536
|
case locks
|
164
537
|
when String, TrueClass, NilClass
|
165
|
-
|
538
|
+
self.lock_value = locks || true
|
166
539
|
else
|
167
|
-
|
540
|
+
self.lock_value = false
|
168
541
|
end
|
169
542
|
|
170
|
-
|
543
|
+
self
|
171
544
|
end
|
172
545
|
|
546
|
+
# Returns a chainable relation with zero records, specifically an
|
547
|
+
# instance of the <tt>ActiveRecord::NullRelation</tt> class.
|
548
|
+
#
|
549
|
+
# The returned <tt>ActiveRecord::NullRelation</tt> inherits from Relation and implements the
|
550
|
+
# Null Object pattern. It is an object with defined null behavior and always returns an empty
|
551
|
+
# array of records without quering the database.
|
552
|
+
#
|
553
|
+
# Any subsequent condition chained to the returned relation will continue
|
554
|
+
# generating an empty relation and will not fire any query to the database.
|
555
|
+
#
|
556
|
+
# Used in cases where a method or scope could return zero records but the
|
557
|
+
# result needs to be chainable.
|
558
|
+
#
|
559
|
+
# For example:
|
560
|
+
#
|
561
|
+
# @posts = current_user.visible_posts.where(name: params[:name])
|
562
|
+
# # => the visible_posts method is expected to return a chainable Relation
|
563
|
+
#
|
564
|
+
# def visible_posts
|
565
|
+
# case role
|
566
|
+
# when 'Country Manager'
|
567
|
+
# Post.where(country: country)
|
568
|
+
# when 'Reviewer'
|
569
|
+
# Post.published
|
570
|
+
# when 'Bad User'
|
571
|
+
# Post.none # => returning [] instead breaks the previous code
|
572
|
+
# end
|
573
|
+
# end
|
574
|
+
#
|
575
|
+
def none
|
576
|
+
extending(NullRelation)
|
577
|
+
end
|
578
|
+
|
579
|
+
def none! # :nodoc:
|
580
|
+
extending!(NullRelation)
|
581
|
+
end
|
582
|
+
|
583
|
+
# Sets readonly attributes for the returned relation. If value is
|
584
|
+
# true (default), attempting to update a record will result in an error.
|
585
|
+
#
|
586
|
+
# users = User.readonly
|
587
|
+
# users.first.save
|
588
|
+
# => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
|
173
589
|
def readonly(value = true)
|
174
|
-
|
175
|
-
|
176
|
-
|
590
|
+
spawn.readonly!(value)
|
591
|
+
end
|
592
|
+
|
593
|
+
def readonly!(value = true) # :nodoc:
|
594
|
+
self.readonly_value = value
|
595
|
+
self
|
177
596
|
end
|
178
597
|
|
598
|
+
# Sets attributes to be used when creating new records from a
|
599
|
+
# relation object.
|
600
|
+
#
|
601
|
+
# users = User.where(name: 'Oscar')
|
602
|
+
# users.new.name # => 'Oscar'
|
603
|
+
#
|
604
|
+
# users = users.create_with(name: 'DHH')
|
605
|
+
# users.new.name # => 'DHH'
|
606
|
+
#
|
607
|
+
# You can pass +nil+ to +create_with+ to reset attributes:
|
608
|
+
#
|
609
|
+
# users = users.create_with(nil)
|
610
|
+
# users.new.name # => 'Oscar'
|
179
611
|
def create_with(value)
|
180
|
-
|
181
|
-
relation.create_with_value = value ? create_with_value.merge(value) : {}
|
182
|
-
relation
|
612
|
+
spawn.create_with!(value)
|
183
613
|
end
|
184
614
|
|
185
|
-
def
|
186
|
-
|
187
|
-
|
188
|
-
|
615
|
+
def create_with!(value) # :nodoc:
|
616
|
+
self.create_with_value = value ? create_with_value.merge(value) : {}
|
617
|
+
self
|
618
|
+
end
|
619
|
+
|
620
|
+
# Specifies table from which the records will be fetched. For example:
|
621
|
+
#
|
622
|
+
# Topic.select('title').from('posts')
|
623
|
+
# #=> SELECT title FROM posts
|
624
|
+
#
|
625
|
+
# Can accept other relation objects. For example:
|
626
|
+
#
|
627
|
+
# Topic.select('title').from(Topic.approved)
|
628
|
+
# # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
|
629
|
+
#
|
630
|
+
# Topic.select('a.title').from(Topic.approved, :a)
|
631
|
+
# # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
|
632
|
+
#
|
633
|
+
def from(value, subquery_name = nil)
|
634
|
+
spawn.from!(value, subquery_name)
|
635
|
+
end
|
636
|
+
|
637
|
+
def from!(value, subquery_name = nil) # :nodoc:
|
638
|
+
self.from_value = [value, subquery_name]
|
639
|
+
self
|
189
640
|
end
|
190
641
|
|
191
642
|
# Specifies whether the records should be unique or not. For example:
|
@@ -199,9 +650,13 @@ module ActiveRecord
|
|
199
650
|
# User.select(:name).uniq.uniq(false)
|
200
651
|
# # => You can also remove the uniqueness
|
201
652
|
def uniq(value = true)
|
202
|
-
|
203
|
-
|
204
|
-
|
653
|
+
spawn.uniq!(value)
|
654
|
+
end
|
655
|
+
|
656
|
+
# Like #uniq, but modifies relation in place.
|
657
|
+
def uniq!(value = true) # :nodoc:
|
658
|
+
self.uniq_value = value
|
659
|
+
self
|
205
660
|
end
|
206
661
|
|
207
662
|
# Used to extend a scope with additional methods, either through
|
@@ -217,16 +672,16 @@ module ActiveRecord
|
|
217
672
|
# end
|
218
673
|
# end
|
219
674
|
#
|
220
|
-
# scope = Model.
|
675
|
+
# scope = Model.all.extending(Pagination)
|
221
676
|
# scope.page(params[:page])
|
222
677
|
#
|
223
678
|
# You can also pass a list of modules:
|
224
679
|
#
|
225
|
-
# scope = Model.
|
680
|
+
# scope = Model.all.extending(Pagination, SomethingElse)
|
226
681
|
#
|
227
682
|
# === Using a block
|
228
683
|
#
|
229
|
-
# scope = Model.
|
684
|
+
# scope = Model.all.extending do
|
230
685
|
# def page(number)
|
231
686
|
# # pagination code goes here
|
232
687
|
# end
|
@@ -235,54 +690,67 @@ module ActiveRecord
|
|
235
690
|
#
|
236
691
|
# You can also use a block and a module list:
|
237
692
|
#
|
238
|
-
# scope = Model.
|
693
|
+
# scope = Model.all.extending(Pagination) do
|
239
694
|
# def per_page(number)
|
240
695
|
# # pagination code goes here
|
241
696
|
# end
|
242
697
|
# end
|
243
|
-
def extending(*modules)
|
244
|
-
modules
|
698
|
+
def extending(*modules, &block)
|
699
|
+
if modules.any? || block
|
700
|
+
spawn.extending!(*modules, &block)
|
701
|
+
else
|
702
|
+
self
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
def extending!(*modules, &block) # :nodoc:
|
707
|
+
modules << Module.new(&block) if block_given?
|
245
708
|
|
246
|
-
|
709
|
+
self.extending_values += modules.flatten
|
710
|
+
extend(*extending_values) if extending_values.any?
|
247
711
|
|
248
|
-
|
249
|
-
relation.send(:apply_modules, modules.flatten)
|
250
|
-
relation
|
712
|
+
self
|
251
713
|
end
|
252
714
|
|
715
|
+
# Reverse the existing order clause on the relation.
|
716
|
+
#
|
717
|
+
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
253
718
|
def reverse_order
|
254
|
-
|
255
|
-
|
256
|
-
|
719
|
+
spawn.reverse_order!
|
720
|
+
end
|
721
|
+
|
722
|
+
def reverse_order! # :nodoc:
|
723
|
+
self.reverse_order_value = !reverse_order_value
|
724
|
+
self
|
257
725
|
end
|
258
726
|
|
727
|
+
# Returns the Arel object associated with the relation.
|
259
728
|
def arel
|
260
729
|
@arel ||= with_default_scope.build_arel
|
261
730
|
end
|
262
731
|
|
732
|
+
# Like #arel, but ignores the default scope of the model.
|
263
733
|
def build_arel
|
264
|
-
arel = table.
|
734
|
+
arel = Arel::SelectManager.new(table.engine, table)
|
265
735
|
|
266
|
-
build_joins(arel,
|
736
|
+
build_joins(arel, joins_values) unless joins_values.empty?
|
267
737
|
|
268
|
-
collapse_wheres(arel, (
|
738
|
+
collapse_wheres(arel, (where_values - ['']).uniq)
|
269
739
|
|
270
|
-
arel.having(
|
740
|
+
arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
|
271
741
|
|
272
|
-
arel.take(connection.sanitize_limit(
|
273
|
-
arel.skip(
|
742
|
+
arel.take(connection.sanitize_limit(limit_value)) if limit_value
|
743
|
+
arel.skip(offset_value.to_i) if offset_value
|
274
744
|
|
275
|
-
arel.group(
|
745
|
+
arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
|
276
746
|
|
277
|
-
|
278
|
-
order = reverse_sql_order(order) if @reverse_order_value
|
279
|
-
arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
|
747
|
+
build_order(arel)
|
280
748
|
|
281
|
-
build_select(arel,
|
749
|
+
build_select(arel, select_values.uniq)
|
282
750
|
|
283
|
-
arel.distinct(
|
284
|
-
arel.from(
|
285
|
-
arel.lock(
|
751
|
+
arel.distinct(uniq_value)
|
752
|
+
arel.from(build_from) if from_value
|
753
|
+
arel.lock(lock_value) if lock_value
|
286
754
|
|
287
755
|
arel
|
288
756
|
end
|
@@ -324,32 +792,48 @@ module ActiveRecord
|
|
324
792
|
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
325
793
|
when Hash
|
326
794
|
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
|
327
|
-
|
795
|
+
|
796
|
+
attributes.values.grep(ActiveRecord::Relation) do |rel|
|
797
|
+
self.bind_values += rel.bind_values
|
798
|
+
end
|
799
|
+
|
800
|
+
PredicateBuilder.build_from_hash(klass, attributes, table)
|
328
801
|
else
|
329
802
|
[opts]
|
330
803
|
end
|
331
804
|
end
|
332
805
|
|
806
|
+
def build_from
|
807
|
+
opts, name = from_value
|
808
|
+
case opts
|
809
|
+
when Relation
|
810
|
+
name ||= 'subquery'
|
811
|
+
opts.arel.as(name.to_s)
|
812
|
+
else
|
813
|
+
opts
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
333
817
|
def build_joins(manager, joins)
|
334
818
|
buckets = joins.group_by do |join|
|
335
819
|
case join
|
336
820
|
when String
|
337
|
-
|
821
|
+
:string_join
|
338
822
|
when Hash, Symbol, Array
|
339
|
-
|
823
|
+
:association_join
|
340
824
|
when ActiveRecord::Associations::JoinDependency::JoinAssociation
|
341
|
-
|
825
|
+
:stashed_join
|
342
826
|
when Arel::Nodes::Join
|
343
|
-
|
827
|
+
:join_node
|
344
828
|
else
|
345
829
|
raise 'unknown class: %s' % join.class.name
|
346
830
|
end
|
347
831
|
end
|
348
832
|
|
349
|
-
association_joins = buckets[
|
350
|
-
stashed_association_joins = buckets[
|
351
|
-
join_nodes = (buckets[
|
352
|
-
string_joins = (buckets[
|
833
|
+
association_joins = buckets[:association_join] || []
|
834
|
+
stashed_association_joins = buckets[:stashed_join] || []
|
835
|
+
join_nodes = (buckets[:join_node] || []).uniq
|
836
|
+
string_joins = (buckets[:string_join] || []).map { |x|
|
353
837
|
x.strip
|
354
838
|
}.uniq
|
355
839
|
|
@@ -384,34 +868,80 @@ module ActiveRecord
|
|
384
868
|
end
|
385
869
|
end
|
386
870
|
|
387
|
-
def apply_modules(modules)
|
388
|
-
unless modules.empty?
|
389
|
-
@extensions += modules
|
390
|
-
modules.each {|extension| extend(extension) }
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
871
|
def reverse_sql_order(order_query)
|
395
872
|
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
|
396
873
|
|
397
|
-
order_query.
|
874
|
+
order_query.flat_map do |o|
|
398
875
|
case o
|
399
876
|
when Arel::Nodes::Ordering
|
400
877
|
o.reverse
|
401
|
-
when String
|
878
|
+
when String
|
402
879
|
o.to_s.split(',').collect do |s|
|
403
880
|
s.strip!
|
404
881
|
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
|
405
882
|
end
|
883
|
+
when Symbol
|
884
|
+
{ o => :desc }
|
885
|
+
when Hash
|
886
|
+
o.each_with_object({}) do |(field, dir), memo|
|
887
|
+
memo[field] = (dir == :asc ? :desc : :asc )
|
888
|
+
end
|
406
889
|
else
|
407
890
|
o
|
408
891
|
end
|
409
|
-
end
|
892
|
+
end
|
410
893
|
end
|
411
894
|
|
412
895
|
def array_of_strings?(o)
|
413
896
|
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
|
414
897
|
end
|
415
898
|
|
899
|
+
def build_order(arel)
|
900
|
+
orders = order_values
|
901
|
+
orders = reverse_sql_order(orders) if reverse_order_value
|
902
|
+
|
903
|
+
orders = orders.uniq.reject(&:blank?).flat_map do |order|
|
904
|
+
case order
|
905
|
+
when Symbol
|
906
|
+
table[order].asc
|
907
|
+
when Hash
|
908
|
+
order.map { |field, dir| table[field].send(dir) }
|
909
|
+
else
|
910
|
+
order
|
911
|
+
end
|
912
|
+
end
|
913
|
+
|
914
|
+
arel.order(*orders) unless orders.empty?
|
915
|
+
end
|
916
|
+
|
917
|
+
def validate_order_args(args)
|
918
|
+
args.select { |a| Hash === a }.each do |h|
|
919
|
+
unless (h.values - [:asc, :desc]).empty?
|
920
|
+
raise ArgumentError, 'Direction should be :asc or :desc'
|
921
|
+
end
|
922
|
+
end
|
923
|
+
end
|
924
|
+
|
925
|
+
# Checks to make sure that the arguments are not blank. Note that if some
|
926
|
+
# blank-like object were initially passed into the query method, then this
|
927
|
+
# method will not raise an error.
|
928
|
+
#
|
929
|
+
# Example:
|
930
|
+
#
|
931
|
+
# Post.references() # => raises an error
|
932
|
+
# Post.references([]) # => does not raise an error
|
933
|
+
#
|
934
|
+
# This particular method should be called with a method_name and the args
|
935
|
+
# passed into that method as an input. For example:
|
936
|
+
#
|
937
|
+
# def references(*args)
|
938
|
+
# check_if_method_has_arguments!("references", args)
|
939
|
+
# ...
|
940
|
+
# end
|
941
|
+
def check_if_method_has_arguments!(method_name, args)
|
942
|
+
if args.blank?
|
943
|
+
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
944
|
+
end
|
945
|
+
end
|
416
946
|
end
|
417
947
|
end
|