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 +4 -4
- data/.gitignore +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +49 -9
- data/lib/separatum/converters/hash2_object.rb +7 -6
- data/lib/separatum/converters/object2_hash.rb +16 -10
- data/lib/separatum/core_ext.rb +15 -0
- data/lib/separatum/exporters/active_record.rb +2 -1
- data/lib/separatum/exporters/active_record_code/object.rb.erb +5 -3
- data/lib/separatum/exporters/active_record_code/program.rb.erb +18 -0
- data/lib/separatum/exporters/active_record_code/transaction.rb.erb +1 -1
- data/lib/separatum/exporters/active_record_code.rb +30 -8
- data/lib/separatum/exporters/json_file.rb +4 -3
- data/lib/separatum/graph_viz/drawer.rb +37 -0
- data/lib/separatum/graph_viz/proxy.rb +30 -0
- data/lib/separatum/importers/active_record.rb +62 -51
- data/lib/separatum/importers/json_file.rb +3 -2
- data/lib/separatum/processors/field_changer.rb +64 -0
- data/lib/separatum/processors/inspect.rb +1 -1
- data/lib/separatum/processors/uuid_changer.rb +17 -5
- data/lib/separatum/version.rb +1 -1
- data/lib/separatum.rb +1 -4
- metadata +7 -4
- data/lib/separatum/object.rb +0 -5
- data/lib/separatum/string.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e83b3b6b180d0cd4bef68738072cfd4c3ba180e5433bee78c2399c51a2e1894
|
4
|
+
data.tar.gz: fdce513260bcd76e4fd58edbe7ea9676206fa065b10e054124b2fd6eca9763b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69aebdd7d04d72853c639205f0f130a5664a7f953d284917d23808a5861221c39f5d09615eefff13cc95c0ae7b71a7a8bbf74944ea75f2c6980c3f50c96d1432
|
7
|
+
data.tar.gz: 10a9dba816fbb93879e671d783b6a2d3df11e6ce2ce1536046e0960955af61bdfa2f1b330148ff84c622d215142380702ff10dc1ab84abcd4acaa46bee87cf0b
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
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::
|
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
|
-
-
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
-
|
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(*
|
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
|
-
|
2
|
-
<%= object.class %>.new(<%= attributes_str.join(', ') %>).save!(validate: false)
|
3
|
-
|
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,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
|
-
|
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(*
|
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
|
-
|
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(*
|
10
|
-
|
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
|
-
|
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] ||
|
10
|
-
@
|
11
|
-
@
|
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
|
-
@
|
14
|
-
@
|
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(
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
32
|
-
|
33
|
-
end
|
43
|
+
# Skip Through associations
|
44
|
+
next if reflection.is_a?(::ActiveRecord::Reflection::ThroughReflection)
|
34
45
|
|
35
|
-
|
36
|
-
|
37
|
-
end
|
46
|
+
# Skip empty associations
|
47
|
+
next if object.send(association_name).nil?
|
38
48
|
|
39
|
-
|
40
|
-
|
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
|
-
|
46
|
-
next
|
47
|
-
|
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
|
-
|
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 |
|
64
|
-
next
|
65
|
-
|
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
|
@@ -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
|
@@ -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.
|
13
|
+
hashes.flatten!
|
14
|
+
hashes.each(&method(:collect_skip_uuid))
|
15
|
+
hashes.map(&method(:transform))
|
12
16
|
end
|
13
17
|
|
14
|
-
def
|
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
|
-
|
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
|
data/lib/separatum/version.rb
CHANGED
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.(
|
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.
|
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-
|
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/
|
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
|
data/lib/separatum/object.rb
DELETED