separatum 0.1.0 → 0.1.1
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/.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