separatum 0.1.0 → 0.1.1

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: b772e3f0272e01367a9e4e8ab1be143964046207677841d6b0532262d6e8a7c1
4
- data.tar.gz: 824578f149e375bbc57bb28cf6b44155610a53c7448c5e27b4e09646d625b951
3
+ metadata.gz: 8e83b3b6b180d0cd4bef68738072cfd4c3ba180e5433bee78c2399c51a2e1894
4
+ data.tar.gz: fdce513260bcd76e4fd58edbe7ea9676206fa065b10e054124b2fd6eca9763b9
5
5
  SHA512:
6
- metadata.gz: 92051fcf37e11b151718a21281a95cc23a6cda366a4891623182ba2f3e183e720ebff0758d48960169ff74c0a2e682c1381d581a49f7c3a5561deaa7a2d32325
7
- data.tar.gz: 457a164b48dcf7b0b6f30bb5d29e55e24784b7257f2d3d448f611f59a2ce16674f84481bda7d93d4e6d4baadeb3a790a7802937e880fd8af7a4bf6e6afdf1ba4
6
+ metadata.gz: 69aebdd7d04d72853c639205f0f130a5664a7f953d284917d23808a5861221c39f5d09615eefff13cc95c0ae7b71a7a8bbf74944ea75f2c6980c3f50c96d1432
7
+ data.tar.gz: 10a9dba816fbb93879e671d783b6a2d3df11e6ce2ce1536046e0960955af61bdfa2f1b330148ff84c622d215142380702ff10dc1ab84abcd4acaa46bee87cf0b
data/.gitignore CHANGED
@@ -12,4 +12,3 @@
12
12
 
13
13
  # rspec failure tracking
14
14
  .rspec_status
15
-
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- separatum (0.1.0)
4
+ separatum (0.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -4,7 +4,7 @@ Extract and transfer linked objects from one database into another.
4
4
 
5
5
  ## How you can use it
6
6
 
7
- - Making seeds.rb as copy of production data for testing purposes.
7
+ - Making seeds.rb as copy of production data for testing purposes
8
8
  - Making separate database for AB-testing (performance or marketing purposes)
9
9
  - Checking your data logical structure (it will raise on broken or unexisting links)
10
10
 
@@ -30,8 +30,7 @@ Build new exporter
30
30
 
31
31
  ```ruby
32
32
  exporter = Separatum.build do
33
- use Separatum::Importers::ActiveRecord # We are going to crawl ActiveRecord objects
34
- use Separatum::Converters::Object2Hash # Most of the modules in Separatum is working with Hash-form of objects
33
+ use Separatum::Importers::ActiveRecord # We are going to crawl ActiveRecord objects
35
34
  use Separatum::Processors::UuidChanger # Hide production's UUIDs with no broken links
36
35
  use Separatum::Exporters::JsonFile, file_name: 'separate.json' # Export object to json file
37
36
  end
@@ -50,8 +49,7 @@ Build new importer
50
49
 
51
50
  ```ruby
52
51
  importer = Separatum.build do
53
- use Separatum::Importers::JsonFile, file_name: 'separate.json' # We are going to import hashed objects from separate.json
54
- use Separatum::Converters::Hash2Object # Convert back to real objects (not persisted)
52
+ use Separatum::Importers::JsonFile, file_name: 'separate.json' # We are going to import hashed objects from separate.json
55
53
  use Separatum::Exporters::ActiveRecord # Save them (in one transaction for all objects in set)
56
54
  end
57
55
  ```
@@ -67,9 +65,7 @@ importer.() # It returns set of persisted objects
67
65
  ```ruby
68
66
  seeds_generator = Separatum.build do
69
67
  use Separatum::Importers::ActiveRecord
70
- use Separatum::Converters::Object2Hash
71
- use Separatum::Processors::UuidChanger
72
- use Separatum::Converters::Hash2Object
68
+ use Separatum::Processors::UuidChanger
73
69
  use Separatum::Exporters::ActiveRecordCode
74
70
  end
75
71
 
@@ -82,9 +78,53 @@ start_object = User.find('any_uuid_you_want_to_start_from')
82
78
  puts seeds_generator.(start_object)
83
79
  ```
84
80
 
81
+ ## Building parts
82
+
83
+ ### Separatum::Importers::ActiveRecord
84
+
85
+ Parameters:
86
+
87
+ - max_depth (default: 3)
88
+ - edge_classes
89
+ - denied_classes
90
+ - denied_class_transitions
91
+ - svg_file_name
92
+ - dot_file_name
93
+
94
+ ### Separatum::Importers::JsonFile
95
+
96
+ Parameters:
97
+
98
+ - file_name
99
+
100
+ ### Separatum::Processors::FieldChanger
101
+
102
+ Parameters:
103
+
104
+ - 1st/2nd - class and field to change
105
+ - 3rd/4th - class and method that will make transformation
106
+ - 3rd - Proc or Block
107
+
108
+ ### Separatum::Exporters::ActiveRecord
109
+
110
+ ### Separatum::Exporters::ActiveRecordCode
111
+
112
+ Parameters:
113
+
114
+ - file_name
115
+ - ignore_not_unique_classes
116
+
117
+
118
+ ### Separatum::Exporters::JsonFile
119
+
120
+ Parameters:
121
+
122
+ - file_name
123
+ - pretty_print
124
+
85
125
  ## TODO
86
126
 
87
- - Data obfuscation (respecting to private data)
127
+ - Timemachine for DateTime fields
88
128
 
89
129
  ## Development
90
130
 
@@ -2,14 +2,15 @@ module Separatum
2
2
  module Converters
3
3
  class Hash2Object
4
4
  def call(*hashes)
5
- hashes.map do |hash|
5
+ hashes.flatten.map do |hash|
6
6
  hash_copy = hash.symbolize_keys
7
- _klass = hash_copy.delete(:_klass).constantize
8
- instance = _klass.new
9
- hash_copy.symbolize_keys.each do |k, v|
10
- instance.send("#{k}=", v)
7
+ klass = Object.const_get(hash_copy.delete(:_klass))
8
+ hash_copy.keys.map(&:to_s).select { |k| '_' == k[0] }.each { |k| hash_copy.delete(k.to_sym) }
9
+ object = klass.new
10
+ hash_copy.each do |k, v|
11
+ object.send("#{k}=", v)
11
12
  end
12
- instance
13
+ object
13
14
  end
14
15
  end
15
16
  end
@@ -1,18 +1,24 @@
1
1
  module Separatum
2
2
  module Converters
3
3
  class Object2Hash
4
+ def initialize(**params)
5
+ @common_fields = params[:common_fields] || {}
6
+ end
7
+
4
8
  def call(*objects)
5
- objects.map do |object|
9
+ objects.flatten.map do |object|
10
+ hash =
11
+ if object.respond_to?(:as_json)
12
+ object.as_json
13
+ elsif object.respond_to?(:to_hash)
14
+ object.to_hash
15
+ elsif object.respond_to?(:to_h)
16
+ object.to_h
17
+ else
18
+ fail
19
+ end
6
20
  klass = object.class.respond_to?(:name) ? object.class.name : object.class.to_s
7
- if object.respond_to?(:as_json)
8
- { _klass: klass }.merge(object.as_json)
9
- elsif object.respond_to?(:to_hash)
10
- { _klass: klass }.merge(object.to_hash)
11
- elsif object.respond_to?(:to_h)
12
- { _klass: klass }.merge(object.to_h)
13
- else
14
- fail(object.inspect)
15
- end
21
+ hash.symbolize_keys.merge(_klass: klass).merge(@common_fields)
16
22
  end
17
23
  end
18
24
  end
@@ -0,0 +1,15 @@
1
+ unless Hash.new.respond_to?(:symbolize_keys)
2
+ class Hash
3
+ def symbolize_keys
4
+ Hash[self.map { |(k, v)| [k.to_sym, v] }]
5
+ end
6
+ end
7
+ end
8
+
9
+ unless String.new.respond_to?(:is_uuid?)
10
+ class String
11
+ def is_uuid?
12
+ !!self.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
13
+ end
14
+ end
15
+ end
@@ -1,7 +1,8 @@
1
1
  module Separatum
2
2
  module Exporters
3
3
  class ActiveRecord
4
- def call(*objects)
4
+ def call(*hashes)
5
+ objects = ::Separatum::Converters::Hash2Object.new.(hashes.flatten)
5
6
  ::ActiveRecord::Base.transaction do
6
7
  objects.each do |o|
7
8
  o.class.connection.execute("ALTER TABLE %s DISABLE TRIGGER ALL;" % [o.class.table_name])
@@ -1,3 +1,5 @@
1
- <%= object.class %>.connection.execute("ALTER TABLE <%= object.class.table_name %> DISABLE TRIGGER ALL;")
2
- <%= object.class %>.new(<%= attributes_str.join(', ') %>).save!(validate: false)
3
- <%= object.class %>.connection.execute("ALTER TABLE <%= object.class.table_name %> ENABLE TRIGGER ALL;")
1
+ <% if ignore_not_unique_classes.include?(object.class) %>
2
+ without_triggers(:<%= object.class.table_name %>) { ignore_record_not_unique { <%= object.class %>.new(<%= attributes_str.join(', ') %>).save!(validate: false) } }
3
+ <% else %>
4
+ without_triggers(:<%= object.class.table_name %>) { <%= object.class %>.new(<%= attributes_str.join(', ') %>).save!(validate: false) }
5
+ <% end %>
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path('../config/environment', __FILE__)
3
+
4
+ def without_triggers(table_name, &block)
5
+ Transactions::CardPayTransaction.connection.execute("ALTER TABLE #{table_name} DISABLE TRIGGER ALL;")
6
+ yield
7
+ Transactions::CardPayTransaction.connection.execute("ALTER TABLE #{table_name} ENABLE TRIGGER ALL;")
8
+ end
9
+
10
+ def ignore_record_not_unique(&block)
11
+ begin
12
+ yield
13
+ rescue ActiveRecord::RecordNotUnique
14
+ nil
15
+ end
16
+ end
17
+
18
+ <%= transactions_str.join("\n") %>
@@ -1,3 +1,3 @@
1
- ::ActiveRecord::Base.transaction do
1
+ ::ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
2
2
  <%= objects_str.join("\n") %>
3
3
  end
@@ -1,26 +1,48 @@
1
1
  module Separatum
2
2
  module Exporters
3
3
  class ActiveRecordCode
4
+ attr_reader :transactions_str, :objects_str, :attributes_str, :key_str, :value_str, :ignore_not_unique_classes
5
+
4
6
  T_TRANSACTION = File.expand_path(File.join(__FILE__, %w(.. active_record_code transaction.rb.erb)))
5
7
  T_OBJECT = File.expand_path(File.join(__FILE__, %w(.. active_record_code object.rb.erb)))
6
8
  T_ATTRIBUTE = File.expand_path(File.join(__FILE__, %w(.. active_record_code attribute.rb.erb)))
9
+ T_PROGRAM = File.expand_path(File.join(__FILE__, %w(.. active_record_code program.rb.erb)))
7
10
 
8
- attr_reader :objects_str, :attributes_str, :key_str, :value_str
11
+ def initialize(**params)
12
+ @file_name = params[:file_name]
13
+ @ignore_not_unique_classes = params[:ignore_not_unique_classes] || []
14
+ end
9
15
 
10
- def call(*objects)
16
+ def call(*hashes)
17
+ objects = ::Separatum::Converters::Hash2Object.new.(hashes.flatten)
11
18
  @objects_str = objects.map do |object|
12
19
  @attributes_str = object.attributes.map do |key, value|
13
20
  @key_str = key
14
- if value.is_a?(ActiveSupport::TimeWithZone)
15
- @value_str = "\"#{value}\""
16
- else
17
- @value_str = value.inspect
18
- end
21
+ @value_str = value_to_code(value)
19
22
  ERB.new(File.read(T_ATTRIBUTE)).result(binding).strip
20
23
  end
21
24
  ERB.new(File.read(T_OBJECT)).result(binding).strip
22
25
  end
23
- ERB.new(File.read(T_TRANSACTION)).result(binding).strip
26
+ @transactions_str = [ERB.new(File.read(T_TRANSACTION)).result(binding).strip]
27
+ script = ERB.new(File.read(T_PROGRAM)).result(binding).strip
28
+
29
+ if @file_name
30
+ File.write(@file_name, script)
31
+ end
32
+
33
+ script
34
+ end
35
+
36
+ def value_to_code(value)
37
+ if defined?(ActiveSupport::TimeWithZone) && value.is_a?(ActiveSupport::TimeWithZone)
38
+ value.to_s.dump
39
+ elsif defined?(Date) && value.is_a?(Date)
40
+ value.to_s.dump
41
+ elsif value.is_a?(String)
42
+ value.dump
43
+ else
44
+ value.inspect
45
+ end
24
46
  end
25
47
  end
26
48
  end
@@ -6,10 +6,11 @@ module Separatum
6
6
  @pretty_print = pretty_print
7
7
  end
8
8
 
9
- def call(*array)
10
- str = @pretty_print ? JSON.pretty_generate(array) : JSON.dump(array)
9
+ def call(*hashes)
10
+ hashes.flatten!
11
+ str = @pretty_print ? JSON.pretty_generate(hashes) : JSON.dump(hashes)
11
12
  File.write(@file_name, str)
12
- array
13
+ hashes
13
14
  end
14
15
  end
15
16
  end
@@ -0,0 +1,37 @@
1
+ module Separatum
2
+ module GraphViz
3
+ class Drawer
4
+ def initialize(svg_file_name: nil, dot_file_name: nil)
5
+ @svg_file_name = svg_file_name
6
+ @dot_file_name = dot_file_name
7
+ @gvp = ::Separatum::GraphViz::Proxy.new(::GraphViz.new(:G, type: :digraph))
8
+ end
9
+
10
+ def draw(object_transitions)
11
+ object_transitions.each do |prev_object, next_object|
12
+ prev_node = @gvp.add_node(object_title(prev_object))
13
+ next_node = @gvp.add_node(object_title(next_object))
14
+ @gvp.add_edge(prev_node, next_node)
15
+ end
16
+
17
+ if @svg_file_name
18
+ @gvp.output(svg: @svg_file_name)
19
+ end
20
+
21
+ if @dot_file_name
22
+ @gvp.output(dot: @dot_file_name)
23
+ end
24
+
25
+ self
26
+ end
27
+
28
+ def object_title(object)
29
+ if object.id.is_a?(String) && object.id.is_uuid?
30
+ "#{object.class}[#{object.id.slice(0, 6)}]"
31
+ else
32
+ "#{object.class}[#{object.id}]"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ module Separatum
2
+ module GraphViz
3
+ class Proxy
4
+ attr_reader :gv
5
+
6
+ def initialize(gv)
7
+ @gv = gv
8
+ @gv[:overlap] = :false
9
+ @nodes = {}
10
+ end
11
+
12
+ def add_node(name, *options)
13
+ if @nodes.key?(name)
14
+ @nodes[name]
15
+ else
16
+ @nodes[name] = @gv.add_node(name, *options)
17
+ end
18
+ end
19
+
20
+ def add_edge(node1, node2)
21
+ @gv.add_edges(node1, node2)
22
+ end
23
+
24
+ # FIXME: change to delegate/forwardable
25
+ def output(*params)
26
+ @gv.output(*params)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -6,81 +6,92 @@ module Separatum
6
6
  attr_reader :klass_transitions, :object_transitions
7
7
 
8
8
  def initialize(**params)
9
- @max_depth = params[:max_depth] || 2
10
- @skip_through = params[:skip_through] || true
11
- @skip_nil = params[:skip_nil] || true
9
+ @max_depth = params[:max_depth] || 3
10
+ @edge_classes = params[:edge_classes] || []
11
+ @denied_classes = params[:denied_classes] || []
12
+ @denied_class_transitions = params[:denied_class_transitions] || []
12
13
 
13
- @skip_klasses = params[:skip_klasses] || []
14
- @skip_objects = params[:skip_objects] || []
15
- @skip_klass_transitions = params[:skip_klass_transitions] || []
16
- @skip_object_transitions = params[:skip_object_transitions] || []
14
+ @svg_file_name = params[:svg_file_name]
15
+ @dot_file_name = params[:dot_file_name]
17
16
 
18
17
  @klass_transitions = Set[]
19
18
  @object_transitions = Set[]
20
19
  @objects = Set[]
21
20
  end
22
21
 
23
- def call(object)
24
- @objects.add(object)
25
- act(object)
26
- @objects.to_a
22
+ def call(*objects)
23
+ result = []
24
+ objects.flatten.each do |object|
25
+ @objects = Set[object]
26
+ act(object)
27
+ result += @objects.to_a
28
+ end
29
+
30
+ if @svg_file_name || @dot_file_name
31
+ if defined? ::GraphViz
32
+ ::Separatum::GraphViz::Drawer.new(svg_file_name: @svg_file_name, dot_file_name: @dot_file_name).draw(@object_transitions)
33
+ else
34
+ fail("GraphViz const is undefined. Use ruby-graphviz gem")
35
+ end
36
+ end
37
+
38
+ ::Separatum::Converters::Object2Hash.new.(result.flatten.uniq)
27
39
  end
28
40
 
29
41
  def act(object, depth = 1)
30
42
  object.class.reflections.each do |association_name, reflection|
31
- if @skip_through && reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
32
- next
33
- end
43
+ # Skip Through associations
44
+ next if reflection.is_a?(::ActiveRecord::Reflection::ThroughReflection)
34
45
 
35
- if @skip_nil && object.send(association_name).nil?
36
- next
37
- end
46
+ # Skip empty associations
47
+ next if object.send(association_name).nil?
38
48
 
39
- if depth >= @max_depth
40
- next
41
- end
49
+ # Limit depth
50
+ next if depth >= @max_depth
42
51
 
43
52
  case reflection.macro
44
53
  when :belongs_to, :has_one
45
- new_object = object.send(association_name)
46
- next if @objects.include?(new_object)
47
- next if new_object.is_one_of_a?(@skip_klasses)
48
-
49
- next if @object_transitions.include?([new_object, object])
50
- next if @skip_objects.include?(new_object)
51
- next if @skip_object_transitions.include?([object, new_object])
52
- next if @skip_klasses.include?(new_object.class)
53
- next if @skip_klass_transitions.include?([object.class, new_object.class])
54
+ next_object = object.send(association_name)
55
+ next unless process_link(object, next_object)
56
+ puts "#{depth}#{' ' * depth}[#{reflection.macro}] #{object.class} -> #{next_object.class}"
57
+ act(next_object, depth + 1)
54
58
 
55
- @objects.add(new_object)
56
- @klass_transitions.add([object.class, new_object.class])
57
- @object_transitions.add([object, new_object])
58
- act(new_object, depth + 1)
59
-
60
-
61
- when :has_many
59
+ when :has_many, :has_and_belongs_to_many
62
60
  new_objects = object.send(association_name)
63
- new_objects.each do |new_object|
64
- next if @objects.include?(new_object)
65
- next if new_object.is_one_of_a?(@skip_klasses)
66
-
67
- next if @object_transitions.include?([new_object, object])
68
- next if @skip_objects.include?(new_object)
69
- next if @skip_object_transitions.include?([object, new_object])
70
- next if @skip_klasses.include?(new_object.class)
71
- next if @skip_klass_transitions.include?([object.class, new_object.class])
72
-
73
- @objects.add(new_object)
74
- @klass_transitions.add([object.class, new_object.class])
75
- @object_transitions.add([object, new_object])
76
- act(new_object, depth + 1)
61
+ new_objects.each do |next_object|
62
+ next unless process_link(object, next_object)
63
+ puts "#{depth}#{' ' * depth}[#{reflection.macro}] #{object.class} -> #{next_object.class}"
64
+ act(next_object, depth + 1)
77
65
  end
78
66
 
79
67
  else
80
- fail
68
+ fail("Unknown association `#{reflection.macro.inspect} :#{association_name}' in #{object.class}")
81
69
  end
82
70
  end
83
71
  end
72
+
73
+ def process_link(prev_object, next_object)
74
+ # Skip already added object
75
+ return if @objects.include?(next_object)
76
+
77
+ # Skip already processed link
78
+ return if @object_transitions.include?([prev_object, next_object])
79
+
80
+ # Stuck on edge classes
81
+ return if @edge_classes.include?(prev_object.class)
82
+
83
+ # Will not go to denied classes
84
+ return if @denied_classes.include?(next_object.class)
85
+
86
+ # Will not go through denied transitions
87
+ return if @denied_class_transitions.include?([prev_object.class, next_object.class])
88
+
89
+ @objects.add(next_object)
90
+ @klass_transitions.add([prev_object.class, next_object.class])
91
+ @object_transitions.add([prev_object, next_object])
92
+
93
+ true
94
+ end
84
95
  end
85
96
  end
86
97
  end
@@ -5,9 +5,10 @@ module Separatum
5
5
  @file_name = file_name
6
6
  end
7
7
 
8
- def call(*_)
8
+ def call(*)
9
9
  str = File.read(@file_name)
10
- JSON.parse(str)
10
+ hash = JSON.parse(str)
11
+ hash.symbolize_keys
11
12
  end
12
13
  end
13
14
  end
@@ -0,0 +1,64 @@
1
+ module Separatum
2
+ module Processors
3
+ class FieldChanger
4
+ # Allows:
5
+ # SomeClass#method
6
+ # SomeNamespace::SomeClass#method
7
+ # SuperNamespace::SomeNamespace::SomeClass#method
8
+ CLASS_METHOD = /^([A-Za-z_][0-9A-Za-z_]*(?:::[A-Za-z_][0-9A-Za-z_]*)*)#([A-Za-z_][0-9A-Za-z_]*)$/
9
+
10
+ def initialize(*options, &block)
11
+ parse_options(*options, &block)
12
+ end
13
+
14
+ def call(*hashes)
15
+ hashes.flatten.map do |hash|
16
+ new_hash = hash.symbolize_keys
17
+ if hash[:_klass] == @klass && hash.key?(@method)
18
+ new_hash[@method] = @transformer.call(hash[@method])
19
+ end
20
+ new_hash
21
+ end
22
+ end
23
+
24
+ def parse_options(*options)
25
+ p1 = options.shift
26
+ if p1.is_a?(String) && (res = p1.match(CLASS_METHOD))
27
+ @klass = res[1]
28
+ @method = res[2].to_sym
29
+ elsif p1.is_a?(Class)
30
+ @klass = p1.to_s
31
+ p2 = options.shift
32
+ if p2.is_a?(Symbol)
33
+ @method = p2
34
+ else
35
+ fail
36
+ end
37
+ else
38
+ fail
39
+ end
40
+
41
+ if block_given?
42
+ @transformer = Proc.new
43
+ pp @transformer
44
+ else
45
+ p3 = options.shift
46
+ if p3.is_a?(Proc)
47
+ @transformer = p3
48
+ elsif p3.is_a?(String) && (res = p3.match(CLASS_METHOD))
49
+ @transformer = Proc.new { |value| Object.const_get(res[1]).new.send(res[2].to_sym, value) }
50
+ elsif p3.is_a?(Class)
51
+ p4 = options.shift
52
+ if p4.is_a?(Symbol)
53
+ @transformer = Proc.new { |value| p3.new.send(p4, value) }
54
+ else
55
+ fail
56
+ end
57
+ else
58
+ fail
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -2,7 +2,7 @@ module Separatum
2
2
  module Processors
3
3
  class Inspect
4
4
  def call(*array)
5
- array.each { |o| puts "#{o.inspect}" }
5
+ array.flatten.each { |o| puts "#{o.inspect}" }
6
6
  array
7
7
  end
8
8
  end
@@ -3,18 +3,30 @@ require 'securerandom'
3
3
  module Separatum
4
4
  module Processors
5
5
  class UuidChanger
6
- def initialize
6
+ def initialize(**params)
7
+ @skip_classes = (params[:skip_classes] || []).map(&:to_s)
8
+ @skip_classes_uuid = Set[]
7
9
  @uuid_map = {}
8
10
  end
9
11
 
10
12
  def call(*hashes)
11
- hashes.map(&method(:transform_hash))
13
+ hashes.flatten!
14
+ hashes.each(&method(:collect_skip_uuid))
15
+ hashes.map(&method(:transform))
12
16
  end
13
17
 
14
- def transform_hash(h)
18
+ def collect_skip_uuid(hash)
19
+ return unless hash.key?(:id)
20
+ return unless hash[:id].to_s.is_uuid?
21
+ return unless hash.key?(:_klass)
22
+ return unless @skip_classes.include?(hash[:_klass])
23
+ @skip_classes_uuid.add(hash[:id])
24
+ end
25
+
26
+ def transform(hash)
15
27
  new_h = {}
16
- h.each do |k, v|
17
- if v.is_a?(String) && v.is_uuid?
28
+ hash.each do |k, v|
29
+ if v.is_a?(String) && v.is_uuid? && !@skip_classes_uuid.include?(v)
18
30
  unless @uuid_map[v]
19
31
  @uuid_map[v] = SecureRandom.uuid
20
32
  end
@@ -1,3 +1,3 @@
1
1
  module Separatum
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/separatum.rb CHANGED
@@ -1,5 +1,3 @@
1
- require "separatum/version"
2
-
3
1
  Dir[File.expand_path(File.join(__FILE__, %w(.. ** *.rb)))].each(&method(:require))
4
2
 
5
3
  module Separatum
@@ -22,12 +20,11 @@ module Separatum
22
20
  end
23
21
 
24
22
  def call(*options)
25
- current_options = options
26
23
  current_result = nil
27
24
  @stack.each do |el|
28
25
  klass, *params = el
29
26
  instance = klass.new(*params)
30
- current_result = instance.(*(current_result || current_options))
27
+ current_result = instance.(current_result || options)
31
28
  end
32
29
  current_result
33
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: separatum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Osenenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-16 00:00:00.000000000 Z
11
+ date: 2019-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -71,18 +71,21 @@ files:
71
71
  - lib/separatum.rb
72
72
  - lib/separatum/converters/hash2_object.rb
73
73
  - lib/separatum/converters/object2_hash.rb
74
+ - lib/separatum/core_ext.rb
74
75
  - lib/separatum/exporters/active_record.rb
75
76
  - lib/separatum/exporters/active_record_code.rb
76
77
  - lib/separatum/exporters/active_record_code/attribute.rb.erb
77
78
  - lib/separatum/exporters/active_record_code/object.rb.erb
79
+ - lib/separatum/exporters/active_record_code/program.rb.erb
78
80
  - lib/separatum/exporters/active_record_code/transaction.rb.erb
79
81
  - lib/separatum/exporters/json_file.rb
82
+ - lib/separatum/graph_viz/drawer.rb
83
+ - lib/separatum/graph_viz/proxy.rb
80
84
  - lib/separatum/importers/active_record.rb
81
85
  - lib/separatum/importers/json_file.rb
82
- - lib/separatum/object.rb
86
+ - lib/separatum/processors/field_changer.rb
83
87
  - lib/separatum/processors/inspect.rb
84
88
  - lib/separatum/processors/uuid_changer.rb
85
- - lib/separatum/string.rb
86
89
  - lib/separatum/version.rb
87
90
  - separatum.gemspec
88
91
  homepage: https://github.com/a0s/separatum
@@ -1,5 +0,0 @@
1
- class Object
2
- def is_one_of_a?(*types)
3
- types.flatten.any? { |t| self.class.to_s == t.to_s }
4
- end
5
- end
@@ -1,5 +0,0 @@
1
- class String
2
- def is_uuid?
3
- !!self.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/)
4
- end
5
- end