dependency-trees 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +6 -0
- data/dependency-trees-0.0.1.gem +0 -0
- data/dependency-trees-0.0.2.gem +0 -0
- data/dependency-trees.gemspec +30 -0
- data/lib/dependency-trees.rb +1 -0
- data/lib/id_hash.rb +97 -0
- data/lib/ids_of_all_dependencies.rb +330 -0
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d1e14f0d5e6be5bdff85df645d8d4e6a7e19eba6b0e73eea533e6fac5300b36
|
4
|
+
data.tar.gz: c2879fd936f32afc19b64baae53be11d3e6c86c704a7a926c6ee7cdc42580e35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30d35afe8fa2f6a5a1e8f1dcce30b6a8da8c0fe0a9c57c56ee519534ceef75106795b398de531529e7d4584f05526e1f66a83627288ac3e58ec7439fb3c93196
|
7
|
+
data.tar.gz: 5c46bc3acca486a80f9996c0426413edfbbb5cb9ebcec75411aeb0e96e985e2f5daab52be8f8fce01df3f8383156a86dd5b766bc4b5c11d91e8b0aed402d9f11
|
data/Gemfile
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'dependency-trees'
|
3
|
+
s.version = '0.0.3'
|
4
|
+
s.summary = 'Tool for better visualizing and managing dependencies'
|
5
|
+
s.authors = ['Karol Selak']
|
6
|
+
s.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
7
|
+
s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
8
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
9
|
+
end
|
10
|
+
s.executables = Dir.glob('bin/*').map { |f| File.basename(f) }
|
11
|
+
s.require_paths = ["lib"]
|
12
|
+
s.license = 'MIT'
|
13
|
+
|
14
|
+
s.add_dependency 'activerecord'
|
15
|
+
s.add_dependency 'pg'
|
16
|
+
s.add_dependency 'pry'
|
17
|
+
s.add_dependency 'rails'
|
18
|
+
|
19
|
+
s.add_dependency 'bootsnap'
|
20
|
+
s.add_dependency 'tzinfo-data'
|
21
|
+
|
22
|
+
s.add_development_dependency 'brakeman'
|
23
|
+
s.add_development_dependency 'byebug'
|
24
|
+
s.add_development_dependency 'factory_bot'
|
25
|
+
s.add_development_dependency 'rspec-rails'
|
26
|
+
s.add_development_dependency 'listen'
|
27
|
+
s.add_development_dependency 'rubocop', '~> 0.75.1'
|
28
|
+
s.add_development_dependency 'rubocop-rspec'
|
29
|
+
s.add_development_dependency 'database_cleaner-active_record'
|
30
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
include 'ids_of_all_dependencies.rb'
|
data/lib/id_hash.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# require 'model'
|
2
|
+
|
3
|
+
class HashOfArrays < Hash
|
4
|
+
def initialize(hash = {})
|
5
|
+
hash.each do |key, array|
|
6
|
+
self[key] = hash[key].clone
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def clone
|
11
|
+
self.class.new(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def subtract(hash)
|
15
|
+
result = self.clone
|
16
|
+
hash.each do |key, array|
|
17
|
+
next if result[key].nil?
|
18
|
+
|
19
|
+
array.each do |el|
|
20
|
+
result[key].delete(el)
|
21
|
+
end
|
22
|
+
|
23
|
+
result.delete(key) if result[key].empty?
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(key, *values)
|
29
|
+
self[key] = [] if self[key].nil?
|
30
|
+
self[key].concat(values)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.join(*hashes)
|
34
|
+
result = self.new
|
35
|
+
result.join(*hashes)
|
36
|
+
end
|
37
|
+
|
38
|
+
def join(*hashes)
|
39
|
+
hashes.each do |hash|
|
40
|
+
hash.each do |key, array|
|
41
|
+
self.add(key, *array)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def sort_arrays!
|
48
|
+
self.each do |key, array|
|
49
|
+
array.sort!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class IdHash < HashOfArrays
|
55
|
+
def add(key, *values)
|
56
|
+
super(key, *values)
|
57
|
+
self[key].uniq!
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# def with_table_symbols
|
62
|
+
# result = HashOfArrays.new
|
63
|
+
|
64
|
+
# self.each do |name, ids|
|
65
|
+
# symbol = Model.get_model(name).table_name.to_sym
|
66
|
+
# result[symbol] = ids
|
67
|
+
# end
|
68
|
+
|
69
|
+
# result
|
70
|
+
# end
|
71
|
+
|
72
|
+
# def remove_entries_from_db(as_first: [], as_last: [])
|
73
|
+
# exceptionals = as_first + as_last
|
74
|
+
# remove_from_exceptional(as_first)
|
75
|
+
|
76
|
+
# self.each do |name, ids|
|
77
|
+
# next if exceptionals.include?(name)
|
78
|
+
# remove_entries_from_array(name, ids)
|
79
|
+
# end
|
80
|
+
|
81
|
+
# remove_from_exceptional(as_last)
|
82
|
+
# end
|
83
|
+
|
84
|
+
# private
|
85
|
+
|
86
|
+
# def remove_from_exceptional(array)
|
87
|
+
# array.each do |name|
|
88
|
+
# ids = self[name]
|
89
|
+
# remove_entries_from_array(name, ids)
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
|
93
|
+
# def remove_entries_from_array(model_name, ids)
|
94
|
+
# model = Model.get_model(model_name)
|
95
|
+
# model.delete(ids) if model.present?
|
96
|
+
# end
|
97
|
+
end
|
@@ -0,0 +1,330 @@
|
|
1
|
+
require 'id_hash'
|
2
|
+
|
3
|
+
module SymbolsOfAllDirectDependencies
|
4
|
+
def symbols_of_all_direct_dependencies
|
5
|
+
self.class.reflect_on_all_associations.map do |association|
|
6
|
+
next if association.macro == :belongs_to
|
7
|
+
|
8
|
+
association.name
|
9
|
+
end.compact
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module IdsOfAllDirectDependencies
|
14
|
+
include SymbolsOfAllDirectDependencies
|
15
|
+
|
16
|
+
def ids_of_all_direct_dependencies
|
17
|
+
result = IdHash.new
|
18
|
+
|
19
|
+
self.class.reflect_on_all_associations.map do |association|
|
20
|
+
next if association.macro == :belongs_to
|
21
|
+
|
22
|
+
symbol = association.klass.name.underscore.to_sym
|
23
|
+
self.send(association.name).map do |associated_object|
|
24
|
+
result.add(symbol, associated_object.id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module IdsOfAllDependenciesNested
|
33
|
+
def ids_of_all_dependencies_nested(depth = Float::INFINITY)
|
34
|
+
result = depth > 0 ? get_associations(depth) : {}
|
35
|
+
result[:id] = id
|
36
|
+
result = result[:id] if result.size == 1
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def get_associations(depth)
|
43
|
+
result = {}
|
44
|
+
|
45
|
+
self.class.reflect_on_all_associations.map do |association|
|
46
|
+
next if association.macro == :belongs_to
|
47
|
+
|
48
|
+
symbol = association.klass.name.underscore.to_sym
|
49
|
+
self.send(association.name).sort_by(&:id).map do |associated_object|
|
50
|
+
result[symbol] = [] if result[symbol].nil?
|
51
|
+
result[symbol] << associated_object.ids_of_all_dependencies_nested(depth - 1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module DependencyTree
|
60
|
+
class Tree < Hash
|
61
|
+
def initialize(hash={})
|
62
|
+
hash.each do |key, value|
|
63
|
+
self[key] = value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def ids_tree
|
68
|
+
self_cloned = self.deep_tree_clone
|
69
|
+
self_cloned.delete_key_recursive(:instance)
|
70
|
+
end
|
71
|
+
|
72
|
+
def status_tree
|
73
|
+
self_cloned = self.deep_tree_clone
|
74
|
+
|
75
|
+
self_cloned.do_recursive do |tree|
|
76
|
+
begin
|
77
|
+
tree[:instance].reload
|
78
|
+
tree[:status] = 'present'
|
79
|
+
rescue ActiveRecord::RecordNotFound => e
|
80
|
+
tree[:status] = 'removed'
|
81
|
+
end
|
82
|
+
|
83
|
+
tree.delete(:instance)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def status_tree_condensed
|
88
|
+
result = status_tree.do_recursive do |tree|
|
89
|
+
tree.each do |name, array|
|
90
|
+
next unless array.class == Array
|
91
|
+
|
92
|
+
new_array = array.map do |subtree|
|
93
|
+
next subtree.root_duplicate_summary if subtree[:duplicate]
|
94
|
+
next subtree if subtree.class != Tree || subtree.size > 2
|
95
|
+
|
96
|
+
subtree.root_summary
|
97
|
+
end
|
98
|
+
|
99
|
+
array.clear
|
100
|
+
array.concat(new_array)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
result.do_recursive do |tree|
|
105
|
+
tree[:_] = tree.root_summary
|
106
|
+
tree.delete(:id)
|
107
|
+
tree.delete(:status)
|
108
|
+
tree.last_to_beginning!
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def last_to_beginning!
|
113
|
+
arr = self.to_a
|
114
|
+
arr.unshift(arr.pop)
|
115
|
+
self.clear
|
116
|
+
self.merge!(arr.to_h)
|
117
|
+
end
|
118
|
+
|
119
|
+
def root_summary
|
120
|
+
"id #{self[:id]}, #{self[:status]}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def root_duplicate_summary
|
124
|
+
root_summary + ", duplicate"
|
125
|
+
end
|
126
|
+
|
127
|
+
def deep_tree_clone
|
128
|
+
hash = self.clone.map do |key, array|
|
129
|
+
next [key, array] unless array.class == Array
|
130
|
+
|
131
|
+
new_array = array.map do |subtree|
|
132
|
+
if subtree.class == Tree
|
133
|
+
subtree.deep_tree_clone
|
134
|
+
else
|
135
|
+
subtree
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
[key, new_array]
|
140
|
+
end
|
141
|
+
|
142
|
+
Tree.new(hash)
|
143
|
+
end
|
144
|
+
|
145
|
+
def delete_key_recursive(key)
|
146
|
+
call_method_recursive(:delete, key)
|
147
|
+
end
|
148
|
+
|
149
|
+
def call_method_recursive(method, *args, &block)
|
150
|
+
do_recursive do |tree|
|
151
|
+
tree.send(method, *args, &block)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def do_recursive(&block)
|
156
|
+
block.call(self)
|
157
|
+
|
158
|
+
self.each do |key, array|
|
159
|
+
next unless array.is_a? Array
|
160
|
+
|
161
|
+
array.each do |subtree|
|
162
|
+
subtree.do_recursive(&block) if subtree.is_a? Tree
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
self
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def dependency_tree(depth = Float::INFINITY, hash_for_duplication_check = IdHash.new)
|
171
|
+
is_duplicate = hash_for_duplication_check[self.class]&.include?(id)
|
172
|
+
hash_for_duplication_check.add(self.class, id)
|
173
|
+
shoud_go_deeper = depth > 0 && !is_duplicate
|
174
|
+
result = shoud_go_deeper ? get_associations_for_tree(depth, hash_for_duplication_check) : Tree.new
|
175
|
+
result[:id] = id
|
176
|
+
result[:instance] = self
|
177
|
+
result[:duplicate] = true if is_duplicate
|
178
|
+
result
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def get_associations_for_tree(depth, hash_for_duplication_check)
|
184
|
+
result = Tree.new
|
185
|
+
|
186
|
+
self.class.reflect_on_all_associations.map do |association|
|
187
|
+
next if association.macro == :belongs_to
|
188
|
+
|
189
|
+
symbol = association.klass.name.underscore.to_sym
|
190
|
+
self.send(association.name).sort_by(&:id).map do |associated_object|
|
191
|
+
result[symbol] = [] if result[symbol].nil?
|
192
|
+
result[symbol] << associated_object.dependency_tree(depth - 1, hash_for_duplication_check)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
result
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
module IdsOfAllDependencies
|
201
|
+
include IdsOfAllDependenciesNested
|
202
|
+
include IdsOfAllDirectDependencies
|
203
|
+
include DependencyTree
|
204
|
+
|
205
|
+
def ids_of_all_dependencies(to_filter=nil, filtering_strategy=:with_parents)
|
206
|
+
ids_of_all_dependencies_with_filtered(to_filter, filtering_strategy)[:main]
|
207
|
+
end
|
208
|
+
|
209
|
+
def ids_of_all_dependencies_with_filtered(to_filter=nil, filtering_strategy=:with_parents)
|
210
|
+
id_hash = ids_of_all_dependencies_without_reflection(to_filter || {}, filtering_strategy)
|
211
|
+
move_wrongly_assigned_to_main(to_filter, id_hash) if to_filter && filtering_strategy == :with_parents
|
212
|
+
id_hash[:main].sort_arrays!
|
213
|
+
id_hash[:filtered_out].sort_arrays!
|
214
|
+
id_hash
|
215
|
+
end
|
216
|
+
|
217
|
+
def ids_of_all_dependencies_without_reflection(to_filter, filtering_strategy=:with_parents)
|
218
|
+
result = { main: IdHash.new, filtered_out: IdHash.new }
|
219
|
+
self_symbol = self.class.name.underscore.to_sym
|
220
|
+
|
221
|
+
self.class.reflect_on_all_associations.map do |association|
|
222
|
+
next if association.macro == :belongs_to
|
223
|
+
symbol = association.klass.name.underscore.to_sym
|
224
|
+
context = { to_filter: to_filter, self_symbol: self_symbol, association: association, strategy: filtering_strategy }
|
225
|
+
|
226
|
+
self.send(association.name).map do |associated_object|
|
227
|
+
hash_to_use = get_hash_to_use(result, **context, object: associated_object)
|
228
|
+
hash_to_use.add(symbol, associated_object.id)
|
229
|
+
end
|
230
|
+
|
231
|
+
result = get_result_with_grandchildren_hashes(result, context)
|
232
|
+
end
|
233
|
+
|
234
|
+
result
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
def get_result_with_grandchildren_hashes(result, context)
|
240
|
+
hashes = get_grandchildren_hashes(context)
|
241
|
+
main = hashes.map { |hash| hash[:main] }
|
242
|
+
filtered_out = hashes.map { |hash| hash[:filtered_out] }
|
243
|
+
|
244
|
+
result[:main] =result[:main].join(*main)
|
245
|
+
result[:filtered_out] = result[:filtered_out].join(*filtered_out)
|
246
|
+
result
|
247
|
+
end
|
248
|
+
|
249
|
+
def get_grandchildren_hashes(context)
|
250
|
+
association = context[:association]
|
251
|
+
to_filter = context[:to_filter]
|
252
|
+
|
253
|
+
self.send(association.name).map do |associated_object|
|
254
|
+
next if should_be_filtered?(**context, object: associated_object)
|
255
|
+
associated_object.ids_of_all_dependencies_without_reflection(to_filter, context[:strategy])
|
256
|
+
end.compact
|
257
|
+
end
|
258
|
+
|
259
|
+
def get_hash_to_use(result, context)
|
260
|
+
symbol = should_be_filtered?(**context) ? :filtered_out : :main
|
261
|
+
result[symbol]
|
262
|
+
end
|
263
|
+
|
264
|
+
def should_be_filtered?(context)
|
265
|
+
case context[:strategy]
|
266
|
+
when :with_parents
|
267
|
+
should_be_filtered_according_to_with_parents_strategy?(context)
|
268
|
+
when :without_parents
|
269
|
+
should_be_filtered_according_to_without_parents_strategy?(context)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def should_be_filtered_according_to_with_parents_strategy?(context)
|
274
|
+
to_filter = context[:to_filter]
|
275
|
+
object = context[:object]
|
276
|
+
association = context[:association]
|
277
|
+
symbol = association.klass.name.underscore.to_sym
|
278
|
+
|
279
|
+
association.klass.reflect_on_all_associations.each do |association2|
|
280
|
+
next if association2.macro == :belongs_to
|
281
|
+
|
282
|
+
context = { to_filter: to_filter, symbol: symbol, association: association2 }
|
283
|
+
return true if object.send(association2.name).any? && is_this_association_filtered?(**context)
|
284
|
+
end
|
285
|
+
|
286
|
+
false
|
287
|
+
end
|
288
|
+
|
289
|
+
def is_this_association_filtered?(to_filter:, symbol:, association:)
|
290
|
+
arr = to_filter[symbol]
|
291
|
+
arr.present? && arr.any? { |a| a == association.name }
|
292
|
+
end
|
293
|
+
|
294
|
+
def should_be_filtered_according_to_without_parents_strategy?(context)
|
295
|
+
is_this_association_filtered?(
|
296
|
+
to_filter: context[:to_filter],
|
297
|
+
symbol: context[:self_symbol],
|
298
|
+
association: context[:association]
|
299
|
+
)
|
300
|
+
end
|
301
|
+
|
302
|
+
def move_wrongly_assigned_to_main(to_filter, id_hash)
|
303
|
+
id_hash[:filtered_out].each do |model_symbol, array|
|
304
|
+
array.clone.each do |id|
|
305
|
+
object = Model.get_model(model_symbol).find(id)
|
306
|
+
move_object_to_main_if_necessary(to_filter[model_symbol], id_hash, object)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
id_hash
|
310
|
+
end
|
311
|
+
|
312
|
+
def move_object_to_main_if_necessary(associations_to_filter, id_hash, object)
|
313
|
+
if should_object_be_moved?(associations_to_filter, id_hash, object)
|
314
|
+
symbol = object.class.name.underscore.to_sym
|
315
|
+
id_hash[:filtered_out][symbol].delete(object.id)
|
316
|
+
id_hash[:main].add(symbol, object.id)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def should_object_be_moved?(associations_to_filter, id_hash, object)
|
321
|
+
associations_to_filter.map do |association|
|
322
|
+
associated = object.send(association)
|
323
|
+
|
324
|
+
associated.to_a.empty? || associated.map do |associated_object|
|
325
|
+
class_symbol = associated_object.class.name.underscore.to_sym
|
326
|
+
id_hash[:main][class_symbol]&.include?(associated_object.id)
|
327
|
+
end.reduce(:&)
|
328
|
+
end.reduce(:&)
|
329
|
+
end
|
330
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dependency-trees
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karol Selak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-09-
|
11
|
+
date: 2022-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -211,7 +211,14 @@ email:
|
|
211
211
|
executables: []
|
212
212
|
extensions: []
|
213
213
|
extra_rdoc_files: []
|
214
|
-
files:
|
214
|
+
files:
|
215
|
+
- Gemfile
|
216
|
+
- dependency-trees-0.0.1.gem
|
217
|
+
- dependency-trees-0.0.2.gem
|
218
|
+
- dependency-trees.gemspec
|
219
|
+
- lib/dependency-trees.rb
|
220
|
+
- lib/id_hash.rb
|
221
|
+
- lib/ids_of_all_dependencies.rb
|
215
222
|
homepage:
|
216
223
|
licenses:
|
217
224
|
- MIT
|