Class: TagHierarchyBuilder
- Inherits:
-
Object
- Object
- TagHierarchyBuilder
show all
- Defined in:
- lib/tag_hierarchy_builder.rb
Defined Under Namespace
Classes: WrongSpecificationSyntax
Class Method Summary
collapse
Class Method Details
.dump_hierarchy ⇒ Object
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
# File 'lib/tag_hierarchy_builder.rb', line 157
def self.dump_hierarchy
tags_chains = Tag.with_joined_hierarchy.without_children.with_parents.find(:all).map { |x| [x] }
while chain = tags_chains.detect { |chain| !chain.first.parents.empty? }
tags_chains.delete(chain)
chain.first.parents.each do |parent|
raise Tag::HierarchyCycle.new if chain.include?(parent)
tags_chains << ([parent] + chain)
end
end
tags_chains.map { |chain| chain.map { |tag| tag.name } }.sort_by { |chain| chain * ' ' }
end
|
.dump_orphans ⇒ Object
181
182
183
184
185
|
# File 'lib/tag_hierarchy_builder.rb', line 181
def self.dump_orphans
Tag.with_joined_hierarchy_and_synonyms.without_children.without_parents.without_synonyms.
pluck(:name)
end
|
.dump_synonyms ⇒ Object
172
173
174
175
176
177
178
179
|
# File 'lib/tag_hierarchy_builder.rb', line 172
def self.dump_synonyms
tags_with_synonyms = Tag.includes(:synonyms)
tags_with_synonyms.map { |t|
[ t.name, *t.synonyms.pluck(:name).sort ]
}.keep_if(&:second)
end
|
148
149
150
151
152
153
154
155
|
# File 'lib/tag_hierarchy_builder.rb', line 148
def self.dump_tags
['# Categories'] +
dump_hierarchy.map { |chain| chain * ' / '} +
['', '# Synonyms'] +
dump_synonyms.map { |chain| chain * ' = '} +
['', '# Unlinked tags'] +
dump_orphans
end
|
.hierarchy_acyclic? ⇒ Boolean
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
# File 'lib/tag_hierarchy_builder.rb', line 125
def self.hierarchy_acyclic?
tags = Tag.includes(:children)
tags_status = tags.map { |x| [ x, :unvisited ] }.flatten
tags_status = Hash[*tags_status]
tags.each do |tag|
next if tags_status[tag] != :unvisited
(reclambda do |this, tag|
return false if tags_status[tag] == :processing
tags_status[tag] = :processing
tag.children.any? do |child|
this.call(child)
end
tags_status[tag] = :closed
end).call(tag)
end
return true
end
|
.instantiate_hierarchy(line) ⇒ Object
112
113
114
115
116
117
118
119
120
121
122
123
|
# File 'lib/tag_hierarchy_builder.rb', line 112
def self.instantiate_hierarchy(line)
line = line.split('/').map(&:strip)
line.each_cons(2) do |(p, c)|
p = Tag.find_or_create_with_like_by_name!(p)
c = Tag.find_or_create_with_like_by_name!(c)
raise Tag::HierarchyCycle.new if c.parents.include?(p) || c == p
c.parents << p
end
end
|
.instantiate_synonyms(line) ⇒ Object
Input should be validated
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
# File 'lib/tag_hierarchy_builder.rb', line 92
def self.instantiate_synonyms(line)
syns = line.split('=').map(&:strip)
b = Tag.find_or_create_with_like_by_name!(syns.shift)
syns.each do |syn|
b.synonyms << Tag.find_or_create_with_like_by_name!(syn)
end
end
|
.rebuild_hierarchy(specification) ⇒ Object
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
|
# File 'lib/tag_hierarchy_builder.rb', line 58
def self.rebuild_hierarchy(specification)
Tag.transaction do
Tag.connection.execute('DELETE from tags_hierarchy')
Tag.connection.execute('DELETE from tags_synonyms')
specification.each do |line|
next if line.blank?
next if line =~ /^\s*#.*/
next if line =~ /^\s*#{Tag::SYMBOL}+\s*$/
begin
if line =~ /^\s*#{Tag::SYMBOL}+\s*(=\s*#{Tag::SYMBOL}+\s*)+$/
instantiate_synonyms(line)
next
end
if line =~ /^\s*#{Tag::SYMBOL}+\s*(\/\s*#{Tag::SYMBOL}+\s*)+$/
instantiate_hierarchy(line)
next
end
raise WrongSpecificationSyntax.new("Line #{line}")
rescue ActiveRecord::RecordInvalid => _
raise WrongSpecificationSyntax.new("Line #{line}")
end
end
hierarchy_acyclic? or raise Tag::HierarchyCycle
rebuild_transitive_closure
end
end
|
.rebuild_transitive_closure ⇒ Object
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
|
# File 'lib/tag_hierarchy_builder.rb', line 4
def self.rebuild_transitive_closure
Tag.transaction do
Tag.connection.execute('DELETE from tags_transitive_hierarchy')
tags = Tag.includes(:synonyms, :children, :transitive_children)
transitive_children = { }
tags.each do |tag|
transitive_children[tag] = []
tag.children.each do |tag_child|
transitive_children[tag] << tag_child
end
end
visited_tags = []
root = {} ; comp = [] ; stack = []
tags.each do |outer_tag|
next if visited_tags.include?(outer_tag)
reclambda do |this, tag|
visited_tags << tag
root[tag] = tag; stack.push(tag)
tag.children.each do |child|
this.call(child) unless visited_tags.include?(child)
if !comp.include?(child)
root[tag] = visited_tags.index(tag) < visited_tags.index(child) ? tag : child
end
transitive_children[tag] += transitive_children[child]
end
if root[tag] == tag
loop do
w = stack.pop
comp << w
transitive_children[w] = transitive_children[tag]
break if w == tag
end
end
end.call(outer_tag)
end
tags.each do |tag|
tag.synonyms.each do |synonym|
transitive_children[tag] << synonym unless transitive_children[tag].include?(synonym)
transitive_children[synonym] << tag unless transitive_children[synonym].include?(tag)
end
end
tags.each do |tag|
tag.transitive_children = transitive_children[tag].uniq
end
end
end
|