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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af543b3df93b964989a1a4d449230a8e9e0ce546dcc840614478d76ee87d74ad
4
- data.tar.gz: 396c6b5415b1f02e9f74162027ac5f4dc3e4fbe7e85e25a45fb07e6fdbb970a6
3
+ metadata.gz: 5d1e14f0d5e6be5bdff85df645d8d4e6a7e19eba6b0e73eea533e6fac5300b36
4
+ data.tar.gz: c2879fd936f32afc19b64baae53be11d3e6c86c704a7a926c6ee7cdc42580e35
5
5
  SHA512:
6
- metadata.gz: f56e1350b759faca73459d4f2a879b698fbc81c409dd564203e55ae7b1b0c64dbab0253293f10cb53d1b1b6d658bfc9e926141ff74f3a6c1dc84b163e46f6a34
7
- data.tar.gz: 1ce10e47c653c6e1d8580134295704b59a2bbfedb1e7d6ce1bd7026fbb16862ee05fc019393ed05423b9b974fd6c979e9443a1d73b6bbce5a49591b3daa5d211
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,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.1
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-18 00:00:00.000000000 Z
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