data_works 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ module DataWorks
2
+ VERSION = "0.1.1"
3
+ end
4
+
@@ -0,0 +1,127 @@
1
+ require 'graphviz'
2
+ require 'launchy'
3
+
4
+ module DataWorks
5
+ module Visualization
6
+
7
+ def visualize
8
+ @g = Graphviz::Graph.new
9
+ build_nodes
10
+ connect_nodes
11
+ filename = "factoried-data-diagram-#{Time.new.strftime("%Y%m%d%H%M%S")}#{rand(1000)}.png"
12
+ path = begin
13
+ File.join(Rails.root, "tmp", filename)
14
+ rescue NameError => error # if not in Rails environment i.e. dev testing
15
+ root = File.expand_path('../..', File.dirname(__FILE__))
16
+ File.join root, 'tmp', filename
17
+ end
18
+ Graphviz::output(@g, path: path)
19
+ Launchy.open(path)
20
+ end
21
+
22
+ private
23
+
24
+ def get_node_shape(model)
25
+ model.is_a?(ActiveRecord::Base) ? 'oval' :'diamond'
26
+ end
27
+
28
+ def build_nodes
29
+ @nodes = {}
30
+ @data.keys.each do |model_name|
31
+ @nodes[model_name] = @data[model_name].each_with_index.map do |model, i|
32
+ @g.add_node("#{model_name}#{i+1}", shape: get_node_shape(model))
33
+ end
34
+ end
35
+ end
36
+
37
+ def connect_nodes
38
+ model_names = @data.keys
39
+ for model_name1 in model_names
40
+ for model_name2 in model_names
41
+ connect_model_kinds(model_name1, model_name2)
42
+ end
43
+ end
44
+ connect_non_active_record_nodes
45
+ end
46
+
47
+ def connect_non_active_record_nodes
48
+ model_names = @data.keys.select do |k|
49
+ k.to_s.classify.constantize.new.is_a?(ActiveHash::Base)
50
+ end
51
+
52
+ model_names.each do |m|
53
+ children = @data.values.
54
+ flatten.
55
+ reject{|e| e.is_a?(m.to_s.classify.constantize)}.
56
+ select { |e| !!e.class.reflect_on_association(m) }
57
+
58
+ parents = @data[m]
59
+
60
+ children.each do |child|
61
+ parent = parents.detect { |p| p.id == child.send("#{m}_id") }
62
+ connect(parent, child)
63
+ end
64
+ end
65
+ end
66
+
67
+ # accepts symbols like :district, :district_schedule_context
68
+ def connect_model_kinds(model_type1, model_type2)
69
+ return if model_type1 == model_type2
70
+ models = @data[model_type1]
71
+ models.each do |parent|
72
+ find_and_connect_children_to_parent(parent, model_type2)
73
+ end
74
+ end
75
+
76
+ def find_and_connect_children_to_parent(parent, child_type)
77
+ assoc = child_association(parent, child_type)
78
+ return if assoc.nil?
79
+ parent.reload
80
+ children = [parent.send(assoc)].flatten
81
+ for child in children
82
+ connect(parent, child)
83
+ end
84
+ end
85
+
86
+ def child_association(parent, child_type)
87
+ assoc = child_associations_for(parent).detect do |name, obj|
88
+ name.to_s.singularize.to_sym == child_type.to_s.singularize.to_sym
89
+ end
90
+ assoc.try(:first)
91
+ end
92
+
93
+ def child_associations_for(parent)
94
+ return [] unless parent.class.respond_to?(:reflections)
95
+ parent.class.reflections.to_a.select do |name, obj|
96
+ (obj.macro == :has_many || obj.macro == :has_one) && !obj.options[:through]
97
+ end
98
+ end
99
+
100
+ def connect(parent, child)
101
+ child_model_name = model_name_of(child)
102
+ parent_model_name = model_name_of(parent)
103
+ if @data[parent_model_name]
104
+ i = @data[parent_model_name].find_index { |model| model.id == parent.id }
105
+ parent_node = @nodes[parent_model_name][i]
106
+ if parent_node
107
+ if @data[child_model_name]
108
+ i = @data[child_model_name].find_index { |model| model.id == child.id }
109
+ if i.nil? # this factory was not created via the DataWorks
110
+ child_node = @g.add_node("#{child_model_name} (unmanaged)", shape: get_node_shape(child))
111
+ else
112
+ child_node = @nodes[child_model_name][i]
113
+ end
114
+ if child_node
115
+ parent_node.connect(child_node)
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ def model_name_of(model)
123
+ model.class.name.underscore.to_sym
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,111 @@
1
+ module DataWorks
2
+ class Works
3
+
4
+ include Visualization
5
+
6
+ def initialize
7
+ # we keep a registry of all models that we create
8
+ @data = {}
9
+ # keep a registry of the 'current default' model of a given type
10
+ @current_default = {}
11
+ # keep a registry of the 'limiting scope' for parentage
12
+ @bounding_models = {}
13
+ end
14
+
15
+ def method_missing(method_name, *args, &block)
16
+ method_name = method_name.to_s
17
+ if method_name.starts_with? 'add_'
18
+ add_model(method_name, *args)
19
+ else
20
+ get_model(method_name, *args)
21
+ end
22
+ end
23
+
24
+ def find_or_add(model_name)
25
+ record = find(model_name, 1)
26
+ record ? record : send("add_#{model_name}")
27
+ end
28
+
29
+ def was_added(model_name, model)
30
+ model_name = model_name.to_sym
31
+ @data[model_name] ||= []
32
+ @data[model_name] << model
33
+ end
34
+
35
+ def set_current_default(for_model:, to:)
36
+ @current_default[for_model] = to
37
+ end
38
+
39
+ def clear_current_default_for(model)
40
+ @current_default.delete(model)
41
+ end
42
+
43
+ def set_restriction(for_model:, to:, &block)
44
+ if block_given?
45
+ @bounding_models[for_model] = to
46
+ block_return = block.call
47
+ clear_restriction_for(for_model)
48
+ block_return
49
+ elsif
50
+ @bounding_models[for_model] = to
51
+ to
52
+ end
53
+ end
54
+
55
+ def clear_restriction_for(model)
56
+ @bounding_models.delete(model)
57
+ end
58
+
59
+ private
60
+
61
+ def add_model(method_name, *args)
62
+ if method_name =~ /\Aadd_(\w+)\Z/
63
+ model_name = $1
64
+ many = args[0].kind_of? Integer
65
+ end
66
+ if model_name
67
+ grafter = Grafter.new(self, (many ? model_name.singularize : model_name))
68
+ if many
69
+ grafter.add_many(*args)
70
+ else
71
+ grafter.add_one(*args)
72
+ end
73
+ end
74
+ end
75
+
76
+ def get_model(method_name, *args)
77
+ if method_name =~ /\A(\w+?)(\d+)\Z/
78
+ model_name = $1
79
+ index = $2
80
+ elsif method_name =~ /\Athe_(\w+)\Z/
81
+ model_name = $1
82
+ index = 1
83
+ end
84
+ find(model_name, index)
85
+ end
86
+
87
+ def find(model_name, index)
88
+ model_name = model_name.to_sym
89
+ if index == 1 && get_default_for(model_name)
90
+ get_default_for(model_name)
91
+ elsif index == 1
92
+ @data[model_name] ||= []
93
+ @data[model_name].reject{|e| has_invalid_parent?(e)}.first
94
+ else
95
+ @data[model_name] ||= []
96
+ @data[model_name][index.to_i-1]
97
+ end
98
+ end
99
+
100
+ def get_default_for(model)
101
+ @bounding_models[model] || @current_default[model] || nil
102
+ end
103
+
104
+ def has_invalid_parent?(model)
105
+ @bounding_models.each do |k,v|
106
+ return true if (model.respond_to?(k) && model.send(k) != v)
107
+ end
108
+ false
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,6 @@
1
+ namespace :data_works do
2
+ desc "tell data_works that the model relationships are accurate"
3
+ task :bless => :environment do
4
+ DataWorks::StaleRelationshipChecker.create_snapshot!
5
+ end
6
+ end
@@ -0,0 +1,118 @@
1
+ require_relative "helper/data_works_spec_helper"
2
+
3
+ describe TheDataWorks do
4
+ let!(:data) { TheDataWorks.new }
5
+
6
+ describe 'adding a record with no necessary parents' do
7
+ describe "add_pet" do
8
+ it 'creates a Pet record' do
9
+ expect { data.add_pet }.to change(Pet, :count).by(1)
10
+ end
11
+ end
12
+
13
+ describe 'with factory traits' do
14
+ let(:put_a_bird_on_it) { data.add_pet_bird }
15
+
16
+ describe "add_pet_bird" do
17
+ it 'creates a Pet record' do
18
+ expect { put_a_bird_on_it }.to change(Pet, :count).by(1)
19
+ end
20
+ it 'creates a record with the required trait' do
21
+ the_bird = put_a_bird_on_it
22
+ expect( the_bird.kind ).to eq 'Bird'
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ describe 'adding a record with one necessary parent' do
29
+ describe 'add_pet_sitter' do
30
+ it 'creates a PetSitter record' do
31
+ expect { data.add_pet_sitter }.to change(PetSitter, :count).by(1)
32
+ end
33
+ it 'creates an Agency record' do
34
+ expect { data.add_pet_sitter }.to change(Agency, :count).by(1)
35
+ end
36
+ end
37
+
38
+ describe 'with factory traits' do
39
+ describe 'on the child model' do
40
+ let(:da_hoomans_toy) { data.add_hooman_toy }
41
+
42
+ describe "add_hooman_toy" do
43
+ it 'creates a Pet record' do # As it still should
44
+ expect { da_hoomans_toy }.to change(Pet, :count).by(1)
45
+ end
46
+ it 'creates a record with the required trait' do
47
+ robo_vac = da_hoomans_toy
48
+ expect( robo_vac.kind ).to eq 'Robot Vacuum' # add the correct trait
49
+ end
50
+ end
51
+ end
52
+
53
+ describe 'on the child and parent models' do
54
+ let(:a_tiny_bell) { data.add_bell_toy }
55
+
56
+ describe "add_bell_toy" do
57
+ it 'creates a Pet record' do # As it still should
58
+ expect { a_tiny_bell }.to change(Pet, :count).by(1)
59
+ end
60
+ it 'creates a record with the required trait on both models' do
61
+ the_bell = a_tiny_bell
62
+ the_bird = data.the_pet_bird
63
+ expect( the_bell.kind ).to eq 'Bell' # add the correct trait
64
+ expect( the_bird.kind ).to eq 'Bird' # add the correct trait
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'adding a record with multiple necessary parents' do
72
+ describe 'add_pet_sitting_patronage' do
73
+ it 'creates a PetSittingPatronage record' do
74
+ expect { data.add_pet_sitting_patronage }.to change(PetSittingPatronage, :count).by(1)
75
+ end
76
+ it 'creates a Pet record' do
77
+ expect { data.add_pet_sitting_patronage }.to change(Pet, :count).by(1)
78
+ end
79
+ it 'creates a PetSitter record' do
80
+ expect { data.add_pet_sitting_patronage }.to change(PetSitter, :count).by(1)
81
+ end
82
+ it 'creates an Agency record' do # necessary for PetSitter
83
+ expect { data.add_pet_sitting_patronage }.to change(Agency, :count).by(1)
84
+ end
85
+ end
86
+ end
87
+
88
+ describe 'adding multiple records at once' do
89
+ describe 'add_pets(2)' do
90
+ it 'creates two Pet records' do
91
+ expect { data.add_pets(2) }.to change(Pet, :count).by(2)
92
+ end
93
+ end
94
+ describe 'add_pet_sitter(s)' do
95
+ it 'creates a PetSitter record' do
96
+ expect { data.add_pet_sitters(2) }.to change(PetSitter, :count).by(2)
97
+ end
98
+ it 'creates an Agency record' do
99
+ expect { data.add_pet_sitters(2) }.to change(Agency, :count).by(1)
100
+ end
101
+ end
102
+ end
103
+
104
+ describe 'adding a record with custom associations' do
105
+ describe 'add_picture' do
106
+ it 'creates a Picture record' do
107
+ expect { data.add_picture }.to change(Picture, :count).by(1)
108
+ end
109
+ it 'creates a Product record (polymorhpic)' do
110
+ expect { data.add_picture }.to change(Product, :count).by(1)
111
+ end
112
+ it 'creates an Album record (custom foreign key)' do
113
+ expect { data.add_picture }.to change(Album, :count).by(1)
114
+ end
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,78 @@
1
+ require_relative '../lib/data_faker'
2
+
3
+ FactoryGirl.define do
4
+ factory :pet do
5
+ name { fake_string }
6
+ kind { animal_types.sample }
7
+ birth_year { (1914..2014).to_a.sample }
8
+
9
+ trait :bird do
10
+ kind 'Bird'
11
+ end
12
+
13
+ factory :pet_bird, traits: [:bird]
14
+ end
15
+
16
+ factory :agency do
17
+ name { fake_string }
18
+ end
19
+
20
+ factory :address do
21
+ street { fake_string + %w(Street Avenue Lane Road).sample }
22
+ city { fake_word.capitalize }
23
+ state { fake_word.capitalize }
24
+ end
25
+
26
+ factory :pet_food do
27
+ name { "#{animal_types.sample} #{%w(Kibble Chow Food Munchies).sample}" }
28
+ end
29
+
30
+ factory :pet_profile do
31
+ description { "#{fake_string} #{fake_string} #{fake_string}" }
32
+ end
33
+
34
+ factory :pet_sitter do
35
+ name { fake_string }
36
+ kind { Kind.all.sample }
37
+ end
38
+
39
+ factory :pet_sitting_patronage do
40
+ end
41
+
42
+ factory :tag do
43
+ registered_name { fake_string }
44
+ end
45
+
46
+ factory :toy do
47
+ name { "#{%w(Fluffy Rubber Squeeky).sample} #{%w(Ball Stick Shoe Book).sample}" }
48
+
49
+ trait :hooman do
50
+ kind 'Robot Vacuum' # you know the one
51
+ end
52
+
53
+ trait :bell do
54
+ kind 'Bell'
55
+ end
56
+
57
+ factory :hooman_toy, traits: [:hooman]
58
+ factory :bell_toy, traits: [:bell]
59
+ end
60
+
61
+ factory :kind do
62
+ name { fake_word }
63
+ end
64
+
65
+ factory :picture do
66
+ end
67
+
68
+ factory :album do
69
+ name { fake_word }
70
+ end
71
+
72
+ factory :product do
73
+ end
74
+ end
75
+
76
+ def animal_types
77
+ %w(Cat Dog Ferret Moose Octopus)
78
+ end