1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
where_clause = @scope.send(:build_where_clause, opts, rest)
@scope.where_clause += where_clause.invert
@scope
end
# Returns a new relation with joins and where clause to identify
# associated relations.
#
# For example, posts that are associated to a related author:
#
# Post.where.associated(:author)
# # SELECT "posts".* FROM "posts"
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# # WHERE "authors"."id" IS NOT NULL
#
# Additionally, multiple relations can be combined. This will return posts
# associated to both an author and any comments:
#
# Post.where.associated(:author, :comments)
# # SELECT "posts".* FROM "posts"
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
# # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
#
# You can define join type in the scope and +associated+ will not use `JOIN` by default.
#
# Post.left_joins(:author).where.associated(:author)
# # SELECT "posts".* FROM "posts"
# # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
# # WHERE "authors"."id" IS NOT NULL
#
# Post.left_joins(:comments).where.associated(:author)
# # SELECT "posts".* FROM "posts"
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
# # WHERE "author"."id" IS NOT NULL
associations.each do
reflection = scope_association_reflection(association)
unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
@scope.joins!(association)
end
association_conditions = Array(reflection.association_primary_key).index_with(nil)
if reflection.options[:class_name]
self.not(association => association_conditions)
else
self.not(reflection.table_name => association_conditions)
end
end
@scope
end
# Returns a new relation with left outer joins and where clause to identify
# missing relations.
#
# For example, posts that are missing a related author:
#
# Post.where.missing(:author)
# # SELECT "posts".* FROM "posts"
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# # WHERE "authors"."id" IS NULL
#
# Additionally, multiple relations can be combined. This will return posts
# that are missing both an author and any comments:
#
# Post.where.missing(:author, :comments)
# # SELECT "posts".* FROM "posts"
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
associations.each do
reflection = scope_association_reflection(association)
@scope.left_outer_joins!(association)
association_conditions = Array(reflection.association_primary_key).index_with(nil)
if reflection.options[:class_name]
@scope.where!(association => association_conditions)
else
@scope.where!(reflection.table_name => association_conditions)
end
end
@scope
end
private
model = @scope.model
reflection = model._reflect_on_association(association)
unless reflection
raise ArgumentError.new()
end
reflection
end
end
# A wrapper to distinguish CTE joins from other nodes.
# :nodoc:
attr_reader :name
@name = name
end
end
= [].freeze
= {}.freeze
Relation::VALUE_METHODS.each do
method_name, default =
case name
when *Relation::MULTI_VALUE_METHODS
[, ]
when *Relation::SINGLE_VALUE_METHODS
[, name == :create_with ? : ]
when *Relation::CLAUSE_METHODS
[, name == :from ? : ]