Skip to content

Commit f42a62a

Browse files
committed
Add multi-level link expansion for edition links
1 parent 7cad8f3 commit f42a62a

File tree

6 files changed

+276
-48
lines changed

6 files changed

+276
-48
lines changed

Diff for: app/models/link_graph/node_collection_factory.rb

+2-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@ def collection
1515
attr_reader :link_graph, :with_drafts, :parent_node
1616

1717
def links_by_link_type
18-
# We don't support nested edition links
19-
return [] if parent_is_edition?
20-
2118
if root?
2219
link_reference.root_links_by_link_type(
2320
content_id: link_graph.root_content_id,
@@ -27,6 +24,8 @@ def links_by_link_type
2724
else
2825
link_reference.child_links_by_link_type(
2926
content_id: parent_node.content_id,
27+
locale: link_graph.root_locale,
28+
with_drafts: link_graph.with_drafts,
3029
link_types_path: parent_node.link_types_path,
3130
parent_content_ids: parent_node.parent_content_ids,
3231
might_have_own_links: parent_node.might_have_own_links?,

Diff for: app/queries/edition_links.rb

+197-34
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,38 @@ class EditionLinks
33
def self.from(content_id,
44
locale:,
55
with_drafts:,
6-
allowed_link_types: nil)
6+
allowed_link_types: nil,
7+
parent_content_ids: [],
8+
next_allowed_link_types_from: nil,
9+
next_allowed_link_types_to: nil)
710
new(
811
content_id:,
912
mode: :from,
1013
locale:,
1114
with_drafts:,
1215
allowed_link_types:,
16+
parent_content_ids:,
17+
next_allowed_link_types_from:,
18+
next_allowed_link_types_to:,
1319
).call
1420
end
1521

1622
def self.to(content_id,
1723
locale:,
1824
with_drafts:,
19-
allowed_link_types: nil)
25+
allowed_link_types: nil,
26+
parent_content_ids: [],
27+
next_allowed_link_types_from: nil,
28+
next_allowed_link_types_to: nil)
2029
new(
2130
content_id:,
2231
mode: :to,
2332
locale:,
2433
with_drafts:,
2534
allowed_link_types:,
35+
parent_content_ids:,
36+
next_allowed_link_types_from:,
37+
next_allowed_link_types_to:,
2638
).call
2739
end
2840

@@ -34,57 +46,83 @@ def call
3446

3547
private
3648

37-
attr_reader :content_id, :mode, :locale, :with_drafts, :allowed_link_types
49+
attr_reader :content_id,
50+
:mode,
51+
:locale,
52+
:with_drafts,
53+
:allowed_link_types,
54+
:parent_content_ids,
55+
:next_allowed_link_types_from,
56+
:next_allowed_link_types_to
3857

3958
def initialize(
4059
content_id:,
4160
mode:,
4261
locale:,
4362
with_drafts:,
44-
allowed_link_types:
63+
allowed_link_types:,
64+
parent_content_ids: [],
65+
next_allowed_link_types_from: nil,
66+
next_allowed_link_types_to: nil
4567
)
4668
@content_id = content_id
4769
@mode = mode
4870
@locale = locale
4971
@with_drafts = with_drafts
5072
@allowed_link_types = allowed_link_types
73+
@parent_content_ids = parent_content_ids
74+
@next_allowed_link_types_from = next_allowed_link_types_from
75+
@next_allowed_link_types_to = next_allowed_link_types_to
5176
end
5277

5378
def links_results
54-
query = Link.left_joins(edition: :document)
55-
where(query)
56-
.order(link_type: :asc, position: :asc)
57-
.pluck(:link_type, link_field, "documents.locale", "editions.id")
58-
end
79+
condition = {}
80+
# condition[:"documents.locale"] = locale if locale
81+
condition[:link_type] = allowed_link_types if allowed_link_types
5982

60-
def link_field
61-
mode == :from ? :target_content_id : "documents.content_id"
83+
if mode == :from
84+
puts "from"
85+
Link
86+
.left_joins(edition: :document)
87+
.left_joins(:link_set)
88+
.where("documents.content_id": content_id)
89+
.or(Link.where("link_sets.content_id": content_id))
90+
.where(condition)
91+
.where(draft_condition)
92+
.order(link_type: :asc, position: :asc)
93+
.pluck(*fields)
94+
else
95+
puts "to"
96+
links = Link
97+
.left_joins(edition: :document)
98+
.left_joins(:link_set)
99+
.where("links.target_content_id": content_id)
100+
.where(condition)
101+
.where(draft_condition)
102+
.order(link_type: :asc, position: :asc)
103+
.pluck(*fields)
104+
puts content_id
105+
puts links.inspect
106+
puts Link.left_joins(edition: :document).left_joins(:link_set).where("links.target_content_id": content_id).where(condition).inspect
107+
links
108+
end
62109
end
63110

64-
def where(query)
65-
condition = if mode == :from
66-
{ "documents.content_id": content_id }
67-
else
68-
{ target_content_id: content_id }
69-
end
70-
condition[:"documents.locale"] = locale if locale
71-
condition[:link_type] = allowed_link_types if allowed_link_types
72-
query.where(condition).where(draft_condition)
111+
def fields
112+
base_fields = [
113+
:link_type,
114+
mode == :from ? :target_content_id : "documents.content_id",
115+
mode == :from ? :target_content_id : "link_sets.content_id",
116+
"documents.locale",
117+
"editions.id",
118+
]
119+
base_fields << has_own_links_field if check_for_from_children?
120+
base_fields << is_linked_to_field if check_for_to_children?
121+
base_fields
73122
end
74123

75124
def draft_condition
76125
return { editions: { content_store: "live" } } unless with_drafts
77-
78-
<<-SQL.strip_heredoc
79-
CASE WHEN EXISTS (
80-
SELECT 1 FROM editions AS e
81-
WHERE content_store = 'draft'
82-
AND e.document_id = documents.id
83-
)
84-
THEN editions.content_store = 'draft'
85-
ELSE editions.content_store = 'live'
86-
END
87-
SQL
88126
end
89127

90128
def group_results(results)
@@ -98,10 +136,135 @@ def group_results(results)
98136

99137
def result_hash(row)
100138
{
101-
content_id: row[1],
102-
locale: row[2],
103-
edition_id: row[3],
139+
content_id: row[1] || row[2],
140+
locale: row[3],
141+
edition_id: row[4],
142+
has_own_links: has_own_links_result(row),
143+
is_linked_to: is_linked_to_result(row),
104144
}
105145
end
146+
147+
def has_own_links_result(row)
148+
return false unless could_have_from_children?
149+
150+
check_for_from_children? ? row[5] : nil
151+
end
152+
153+
def is_linked_to_result(row)
154+
return false unless could_have_to_children?
155+
156+
check_for_from_children? ? row[6] : row[5]
157+
end
158+
159+
def check_for_from_children?
160+
next_allowed_link_types_from && next_allowed_link_types_from.present?
161+
end
162+
163+
def check_for_to_children?
164+
next_allowed_link_types_to && next_allowed_link_types_to.present?
165+
end
166+
167+
def could_have_from_children?
168+
next_allowed_link_types_from.nil? || next_allowed_link_types_from.present?
169+
end
170+
171+
def could_have_to_children?
172+
next_allowed_link_types_to.nil? || next_allowed_link_types_to.present?
173+
end
174+
175+
def has_own_links_field
176+
if mode == :from
177+
Arel.sql(%{
178+
EXISTS(
179+
SELECT nested_links.id
180+
FROM links AS nested_links
181+
INNER JOIN link_sets AS nested_link_sets
182+
ON nested_link_sets.id = nested_links.link_set_id
183+
WHERE nested_link_sets.content_id = links.target_content_id
184+
#{and_not_parent('nested_links.target_content_id')}
185+
AND (#{allowed_links_condition(next_allowed_link_types_from)})
186+
LIMIT 1
187+
) OR EXISTS(
188+
SELECT nested_links.id
189+
FROM links AS nested_links
190+
INNER JOIN documents AS nested_documents
191+
ON nested_documents.content_id = nested_links.target_content_id
192+
WHERE nested_documents.content_id = links.target_content_id
193+
#{and_not_parent('nested_links.target_content_id')}
194+
AND (#{allowed_links_condition(next_allowed_link_types_from)})
195+
LIMIT 1
196+
)
197+
})
198+
else
199+
Arel.sql(%{
200+
EXISTS(
201+
SELECT nested_links.id
202+
FROM links AS nested_links
203+
INNER JOIN link_sets AS nested_link_sets
204+
ON nested_link_sets.id = nested_links.link_set_id
205+
WHERE nested_links.target_content_id = link_sets.content_id
206+
#{and_not_parent('nested_links.target_content_id')}
207+
AND (#{allowed_links_condition(next_allowed_link_types_from)})
208+
LIMIT 1
209+
) OR EXISTS(
210+
SELECT nested_links.id
211+
FROM links AS nested_links
212+
INNER JOIN documents AS nested_documents
213+
ON nested_documents.content_id = nested_links.target_content_id
214+
WHERE nested_links.target_content_id = documents.content_id
215+
#{and_not_parent('nested_links.target_content_id')}
216+
AND (#{allowed_links_condition(next_allowed_link_types_from)})
217+
LIMIT 1
218+
)
219+
})
220+
end
221+
end
222+
223+
def is_linked_to_field
224+
query = if mode == :from
225+
"nested_links.target_content_id = links.target_content_id"
226+
else
227+
"(nested_links.target_content_id = link_sets.content_id OR nested_links.target_content_id = documents.content_id)"
228+
end
229+
230+
Arel.sql(%{
231+
EXISTS(
232+
SELECT nested_links.id
233+
FROM links AS nested_links
234+
LEFT JOIN link_sets AS nested_link_sets
235+
ON nested_link_sets.id = nested_links.link_set_id
236+
LEFT JOIN documents AS nested_documents
237+
ON nested_documents.content_id = nested_links.target_content_id
238+
WHERE #{query}
239+
#{and_not_parent('nested_link_sets.content_id')}
240+
AND (#{allowed_links_condition(next_allowed_link_types_to)})
241+
LIMIT 1
242+
)
243+
})
244+
end
245+
246+
def and_not_parent(field)
247+
return if parent_content_ids.empty?
248+
249+
quoted = parent_content_ids.map { |c_id| quote(c_id) }
250+
251+
"AND #{field} NOT IN (#{quoted.join(', ')})"
252+
end
253+
254+
def allowed_links_condition(allowed_links)
255+
each_link_type = allowed_links.map do |(link_type, next_links)|
256+
raise "Empty links for #{link_type} on #{content_id}" if next_links.empty?
257+
258+
quoted_next_links = next_links.map { |n| quote(n) }
259+
260+
"(links.link_type = #{quote(link_type)} AND nested_links.link_type IN (#{quoted_next_links.join(', ')}))"
261+
end
262+
263+
each_link_type.join(" OR ")
264+
end
265+
266+
def quote(field)
267+
ActiveRecord::Base.connection.quote(field)
268+
end
106269
end
107270
end

0 commit comments

Comments
 (0)