dependency-trees 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c20c45337081f03a157d37fc0b7c2b4238277ef3fa6c8155fa5dec1aec5916f7
4
- data.tar.gz: 0fba9959ca1e48e1693dca3905f90c54f81e3482dac5a8e8721f0c54d343c828
3
+ metadata.gz: 5d1e14f0d5e6be5bdff85df645d8d4e6a7e19eba6b0e73eea533e6fac5300b36
4
+ data.tar.gz: c2879fd936f32afc19b64baae53be11d3e6c86c704a7a926c6ee7cdc42580e35
5
5
  SHA512:
6
- metadata.gz: '08e214e775fd74f4e80842fcc0a98cf17ad38c12840e773f5774342abb27040cb4ac6be3e0b39d95a3d5eed0a9d702238d76924b37da9fb85efa6135e58117e4'
7
- data.tar.gz: f89fec9da74ad3174431ab5fd42ea27e75d1cf9458cc5867548178705da4cbcc317cc982e37d3bdca7c6851d3b325a925ed6b8c9623cbdca2d658af8b97ae229
6
+ metadata.gz: 30d35afe8fa2f6a5a1e8f1dcce30b6a8da8c0fe0a9c57c56ee519534ceef75106795b398de531529e7d4584f05526e1f66a83627288ac3e58ec7439fb3c93196
7
+ data.tar.gz: 5c46bc3acca486a80f9996c0426413edfbbb5cb9ebcec75411aeb0e96e985e2f5daab52be8f8fce01df3f8383156a86dd5b766bc4b5c11d91e8b0aed402d9f11
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
+
6
+ gemspec
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependency-trees
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karol Selak
@@ -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