ooor 1.4.2 → 1.5.0
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.
- data/README.md +10 -70
- data/lib/app/models/base64.rb +1 -1
- data/lib/app/models/common_service.rb +21 -20
- data/lib/app/models/db_service.rb +22 -20
- data/lib/app/models/ooor_client.rb +41 -0
- data/lib/app/models/open_object_resource.rb +325 -411
- data/lib/app/models/relation.rb +145 -0
- data/lib/app/models/type_casting.rb +119 -0
- data/lib/app/models/uml.rb +173 -150
- data/lib/app/ui/action_window.rb +82 -8
- data/lib/app/ui/client_base.rb +26 -29
- data/lib/app/ui/form_model.rb +63 -62
- data/lib/app/ui/menu.rb +12 -11
- data/lib/ooor.rb +90 -77
- data/spec/ooor_spec.rb +41 -34
- metadata +49 -46
@@ -0,0 +1,145 @@
|
|
1
|
+
# OOOR: Open Object On Rails
|
2
|
+
# Copyright (C) 2009-2011 Akretion LTDA (<http://www.akretion.com>).
|
3
|
+
# Author: Raphaël Valyi
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
#TODO chainability of where via scopes
|
19
|
+
#TODO include relations for single read
|
20
|
+
|
21
|
+
module Ooor
|
22
|
+
# = Similar to Active Record Relation
|
23
|
+
class Relation
|
24
|
+
|
25
|
+
attr_reader :klass, :loaded
|
26
|
+
attr_accessor :context, :count_field, :includes_values, :eager_load_values, :preload_values,
|
27
|
+
:select_values, :group_values, :order_values, :reorder_flag, :joins_values, :where_values, :having_values,
|
28
|
+
:limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
|
29
|
+
alias :loaded? :loaded
|
30
|
+
|
31
|
+
def build_where(opts, other = [])
|
32
|
+
case opts
|
33
|
+
when Array
|
34
|
+
[opts]
|
35
|
+
when Hash
|
36
|
+
opts.keys.map {|key|["#{key}", "=", opts[key]]}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def where(opts, *rest)
|
41
|
+
relation = clone
|
42
|
+
relation.where_values += build_where(opts, rest) unless opts.blank?
|
43
|
+
relation
|
44
|
+
end
|
45
|
+
|
46
|
+
# def having(*args)
|
47
|
+
# relation = clone
|
48
|
+
# relation.having_values += build_where(*args) unless args.blank?
|
49
|
+
# relation
|
50
|
+
# end
|
51
|
+
|
52
|
+
def limit(value)
|
53
|
+
relation = clone
|
54
|
+
relation.limit_value = value
|
55
|
+
relation
|
56
|
+
end
|
57
|
+
|
58
|
+
def offset(value)
|
59
|
+
relation = clone
|
60
|
+
relation.offset_value = value
|
61
|
+
relation
|
62
|
+
end
|
63
|
+
|
64
|
+
def order(*args)
|
65
|
+
relation = clone
|
66
|
+
relation.order_values += args.flatten unless args.blank?
|
67
|
+
relation
|
68
|
+
end
|
69
|
+
|
70
|
+
def count(column_name = nil, options = {})
|
71
|
+
column_name, options = nil, column_name if column_name.is_a?(Hash)
|
72
|
+
calculate(:count, column_name, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(klass)
|
76
|
+
@klass = klass
|
77
|
+
@where_values = []
|
78
|
+
@loaded = false
|
79
|
+
@context = {}
|
80
|
+
@count_field = false
|
81
|
+
@limit_value = false
|
82
|
+
@offset_value = false
|
83
|
+
@order_values = []
|
84
|
+
end
|
85
|
+
|
86
|
+
def new(*args, &block)
|
87
|
+
#TODO inject current domain in *args
|
88
|
+
@klass.new(*args, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
def reload
|
92
|
+
reset
|
93
|
+
to_a # force reload
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize_copy(other)
|
98
|
+
reset
|
99
|
+
end
|
100
|
+
|
101
|
+
def reset
|
102
|
+
@first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
|
103
|
+
@should_eager_load = @join_dependency = nil
|
104
|
+
@records = []
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
# A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
|
109
|
+
# same arguments to this method as you can to <tt>find(:all)</tt>.
|
110
|
+
def all(*args)
|
111
|
+
#args.any? ? apply_finder_options(args.first).to_a : to_a TODO
|
112
|
+
to_a
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_a
|
116
|
+
return @records if loaded?
|
117
|
+
if @order_values.empty?
|
118
|
+
search_order = false
|
119
|
+
else
|
120
|
+
search_order = @order_values.join(", ")
|
121
|
+
end
|
122
|
+
ids = @klass.rpc_execute('search', @where_values, @offset_value, @limit_value, search_order, @context, @count_field)
|
123
|
+
@records = @klass.find(ids)
|
124
|
+
@loaded = true
|
125
|
+
@records
|
126
|
+
end
|
127
|
+
|
128
|
+
def eager_loading?
|
129
|
+
false
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
|
134
|
+
def method_missing(method, *args, &block)
|
135
|
+
if Array.method_defined?(method)
|
136
|
+
to_a.send(method, *args, &block)
|
137
|
+
elsif @klass.respond_to?(method)
|
138
|
+
@klass.send(method, *args, &block)
|
139
|
+
else
|
140
|
+
@klass.rpc_execute(method.to_s, to_a.map {|record| record.id}, *args)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Ooor
|
2
|
+
module TypeCasting
|
3
|
+
|
4
|
+
def self.included(base) base.extend(ClassMethods) end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
def openerp_string_domain_to_ruby(string_domain)
|
9
|
+
eval(string_domain.gsub('(', '[').gsub(')',']'))
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_openerp_domain(domain)
|
13
|
+
if domain.is_a?(Hash)
|
14
|
+
return domain.map{|k,v| [k.to_s, '=', v]}
|
15
|
+
elsif domain == []
|
16
|
+
return []
|
17
|
+
elsif domain.is_a?(Array) && !domain.last.is_a?(Array)
|
18
|
+
return [domain]
|
19
|
+
else
|
20
|
+
return domain
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def clean_request_args!(args)
|
25
|
+
if args[-1].is_a? Hash
|
26
|
+
args[-1] = @ooor.global_context.merge(args[-1])
|
27
|
+
elsif args.is_a?(Array)
|
28
|
+
args += [@ooor.global_context]
|
29
|
+
end
|
30
|
+
cast_request_to_openerp!(args[-2]) if args[-2].is_a? Hash
|
31
|
+
end
|
32
|
+
|
33
|
+
def cast_request_to_openerp!(map)
|
34
|
+
map.each do |k, v|
|
35
|
+
if v == nil
|
36
|
+
map[k] = false
|
37
|
+
elsif !v.is_a?(Integer) && !v.is_a?(Float) && v.is_a?(Numeric) && v.respond_to?(:to_f)
|
38
|
+
map[k] = v.to_f
|
39
|
+
elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:sec) && v.respond_to?(:year)#really ensure that's a datetime type
|
40
|
+
map[k] = "#{v.year}-#{v.month}-#{v.day} #{v.hour}:#{v.min}:#{v.sec}"
|
41
|
+
elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:day) && v.respond_to?(:year)#really ensure that's a date type
|
42
|
+
map[k] = "#{v.year}-#{v.month}-#{v.day}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def cast_answer_to_ruby!(answer)
|
48
|
+
def cast_map_to_ruby!(map)
|
49
|
+
map.each do |k, v|
|
50
|
+
if self.fields[k] && v.is_a?(String) && !v.empty?
|
51
|
+
case self.fields[k]['type']
|
52
|
+
when 'datetime'
|
53
|
+
map[k] = Time.parse(v)
|
54
|
+
when 'date'
|
55
|
+
map[k] = Date.parse(v)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if answer.is_a?(Array)
|
62
|
+
answer.each {|item| self.cast_map_to_ruby!(item) if item.is_a? Hash}
|
63
|
+
elsif answer.is_a?(Hash)
|
64
|
+
self.cast_map_to_ruby!(answer)
|
65
|
+
else
|
66
|
+
answer
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_openerp_hash!
|
73
|
+
cast_relations_to_openerp!
|
74
|
+
@attributes.reject {|k, v| k == 'id'}.merge(@associations)
|
75
|
+
end
|
76
|
+
|
77
|
+
def cast_relations_to_openerp!
|
78
|
+
@associations.reject! do |k, v| #reject non assigned many2one or empty list
|
79
|
+
v.is_a?(Array) && (v.size == 0 or v[1].is_a?(String))
|
80
|
+
end
|
81
|
+
|
82
|
+
def cast_relation(k, v, one2many_associations, many2many_associations)
|
83
|
+
if one2many_associations[k]
|
84
|
+
return v.collect! do |value|
|
85
|
+
if value.is_a?(OpenObjectResource) #on the fly creation as in the GTK client
|
86
|
+
[0, 0, value.to_openerp_hash!]
|
87
|
+
else
|
88
|
+
if value.is_a?(Hash)
|
89
|
+
[0, 0, value]
|
90
|
+
else
|
91
|
+
[1, value, {}]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
elsif many2many_associations[k]
|
96
|
+
return v = [[6, 0, v]]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@associations.each do |k, v| #see OpenERP awkward associations API
|
101
|
+
#already casted, possibly before server error!
|
102
|
+
next if (v.is_a?(Array) && v.size == 1 && v[0].is_a?(Array)) \
|
103
|
+
|| self.class.many2one_associations[k] \
|
104
|
+
|| !v.is_a?(Array)
|
105
|
+
new_rel = self.cast_relation(k, v, self.class.one2many_associations, self.class.many2many_associations)
|
106
|
+
if new_rel #matches a known o2m or m2m
|
107
|
+
@associations[k] = new_rel
|
108
|
+
else
|
109
|
+
self.class.many2one_associations.each do |k2, field| #try to cast the association to an inherited o2m or m2m:
|
110
|
+
linked_class = self.class.const_get(field['relation'])
|
111
|
+
new_rel = self.cast_relation(k, v, linked_class.one2many_associations, linked_class.many2many_associations)
|
112
|
+
@associations[k] = new_rel and break if new_rel
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
data/lib/app/models/uml.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# OOOR: Open Object On Rails
|
2
|
-
# Copyright (C) 2009-
|
2
|
+
# Copyright (C) 2009-2011 Akretion LTDA (<http://www.akretion.com>).
|
3
3
|
# Author: Raphaël Valyi
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
@@ -14,183 +14,206 @@
|
|
14
14
|
#
|
15
15
|
# You should have received a copy of the GNU Affero General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
module Ooor
|
18
|
+
module UML
|
19
|
+
#usage: UML.print_uml or with options: UML.print_uml(:all, :detailed) or MyOpenObjectResource.print_uml or UML.print_uml([list_of_classes], :all, :detailed)
|
17
20
|
|
18
|
-
|
19
|
-
#usage: UML.print_uml or with options: UML.print_uml(:all, : detailed) or MyOpenObjectResource.print_uml or UML.print_uml([list_of_classes], :all, :detailed)
|
21
|
+
def self.included(base) base.extend(ClassMethods) end
|
20
22
|
|
21
|
-
def self.included(base) base.extend(ClassMethods) end
|
22
|
-
|
23
|
-
def print_uml(*options)
|
24
|
-
ooor = self.class.ooor
|
25
|
-
UML.print_uml(ooor.config[:models] && ooor.loaded_models.select {|model| ooor.config[:models].index(model.openerp_model)} || ooor.loaded_models, options)
|
26
|
-
end
|
27
|
-
|
28
|
-
module ClassMethods
|
29
23
|
def print_uml(*options)
|
30
|
-
|
24
|
+
ooor = self.class.ooor
|
25
|
+
UML.print_uml(ooor.config[:models] && ooor.loaded_models.select {|model| ooor.config[:models].index(model.openerp_model)} || ooor.loaded_models, options)
|
31
26
|
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.display_fields(clazz)
|
35
|
-
s = ""
|
36
|
-
clazz.reload_fields_definition if clazz.fields.empty?
|
37
|
-
clazz.fields.sort {|a,b| a[1]['type'] <=> b[1]['type']}.each {|i| s << "+ #{i[1]['type']} : #{i[0]}\\l\\n"}
|
38
|
-
s
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.print_uml(classes, *options)
|
42
|
-
options = options[0] if options[0].is_a?(Array)
|
43
|
-
local = (options.index(:all) == nil)
|
44
|
-
detailed = (options.index(:detailed) != nil) || local && (options.index(:nodetail) == nil)
|
45
|
-
|
46
|
-
enabled_targets = classes[0].ooor.config[:models] #defines the scope of the UML for option local
|
47
|
-
m2o_edges = {}
|
48
|
-
o2m_edges = {}
|
49
|
-
m2m_edges = {}
|
50
|
-
#instead of diplaying several relations of the same kind between two nodes which would bloat the graph,
|
51
|
-
#we track them all and factor them on a common multiline edge label:
|
52
|
-
connex_classes = UML.collect_edges(false, local, classes, enabled_targets, m2o_edges, o2m_edges, m2m_edges)
|
53
|
-
#back links from connex classes:
|
54
|
-
connex_classes += UML.collect_edges(true, local, connex_classes - classes, classes, m2o_edges, o2m_edges, m2m_edges)
|
55
|
-
|
56
|
-
File.open('uml.dot', 'w') do |f|
|
57
|
-
f << <<-eos
|
58
|
-
digraph G {
|
59
|
-
fontname = "Bitstream Vera Sans"
|
60
|
-
fontsize = 8
|
61
|
-
label = "*** generated by OOOR by www.akretion.com ***"
|
62
|
-
node [
|
63
|
-
fontname = "Bitstream Vera Sans"
|
64
|
-
fontsize = 16
|
65
|
-
shape = "record"
|
66
|
-
fillcolor=orange
|
67
|
-
style="rounded,filled"
|
68
|
-
]
|
69
|
-
edge [
|
70
|
-
arrowhead = "none"
|
71
|
-
fontname = "Bitstream Vera Sans"
|
72
|
-
fontsize = 9
|
73
|
-
]
|
74
|
-
eos
|
75
|
-
|
76
|
-
#UML nodes definitions
|
77
|
-
((connex_classes - classes) + classes - [IrModel, IrModelFields]).each do |model|
|
78
|
-
f << " #{model} [ label = \"{#{model.name}#{detailed ? '|' + display_fields(model) : ''}}\" ]"
|
79
|
-
end
|
80
27
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
28
|
+
def serve_uml(*options)#TODO port
|
29
|
+
ooor = self
|
30
|
+
require 'sinatra'
|
31
|
+
set :public, File.dirname(__FILE__) + '/../../'
|
32
|
+
|
33
|
+
get '/model/:model_name' do
|
34
|
+
model_name = params[:model_name]
|
35
|
+
model = ooor.const_get(model_name)
|
36
|
+
model.print_uml
|
37
|
+
s = ""#"Hello world! #{model_name}"
|
38
|
+
s << "<IMG SRC='/x.png' USEMAP='#UMLbyOOOR' />"
|
39
|
+
f=File.open('x.map')
|
40
|
+
f.each_line {|l| s << l}
|
41
|
+
s
|
92
42
|
end
|
93
43
|
|
94
|
-
|
95
|
-
|
96
|
-
edge [
|
97
|
-
headlabel = "n"
|
98
|
-
taillabel = "1"
|
99
|
-
]
|
100
|
-
eos
|
101
|
-
o2m_edges.each do |k, v|
|
102
|
-
f << "edge [label = \"#{v[3].join("\\n")}\"]\n"
|
103
|
-
f << "#{v[0]} -> #{v[1]}\n"
|
104
|
-
end
|
44
|
+
Sinatra::Application.run!
|
45
|
+
end
|
105
46
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
headlabel = "n"
|
110
|
-
taillabel = "n"
|
111
|
-
]
|
112
|
-
eos
|
113
|
-
m2m_edges.each do |k, v|
|
114
|
-
reverse_part = v[3].size > 0 ? "\\n/#{v[3].join("\\n")}\"]\n" : "\"]\n"
|
115
|
-
f << "edge [label = \"#{v[2].join("\\n")}}#{reverse_part}"
|
116
|
-
f << "#{v[0]} -> #{v[1]}\n"
|
47
|
+
module ClassMethods
|
48
|
+
def print_uml(*options)
|
49
|
+
UML.print_uml([self], options)
|
117
50
|
end
|
118
|
-
|
119
|
-
f << "}"
|
120
51
|
end
|
121
52
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
53
|
+
def self.display_fields(clazz)
|
54
|
+
s = ""
|
55
|
+
clazz.reload_fields_definition if clazz.fields.empty?
|
56
|
+
#clazz.fields.sort {|a,b| a[1]['type'] <=> b[1]['type']}.each {|i| s << "+ #{i[1]['type']} : #{i[0]}\\l\\n"}
|
57
|
+
clazz.fields.sort {|a,b| a[1]['type'] <=> b[1]['type']}.each {|i| s << "<TR><TD COLSPAN=\"2\" BGCOLOR=\"#bed1b8\" ALIGN=\"LEFT\">#{i[0]}</TD><TD ALIGN=\"LEFT\">#{i[1]['type']}</TD></TR>\\l\\n"}
|
58
|
+
s
|
126
59
|
end
|
127
|
-
cmd_line2 = "dot -Tpng uml.dot -o uml.png"
|
128
|
-
system(cmd_line2)
|
129
|
-
end
|
130
60
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
61
|
+
def self.print_uml(classes, *options)
|
62
|
+
options = options[0] if options[0].is_a?(Array)
|
63
|
+
local = (options.index(:all) == nil)
|
64
|
+
detailed = (options.index(:detailed) != nil) || local && (options.index(:nodetail) == nil)
|
65
|
+
|
66
|
+
enabled_targets = classes[0].ooor.config[:models] #defines the scope of the UML for option local
|
67
|
+
m2o_edges = {}
|
68
|
+
o2m_edges = {}
|
69
|
+
m2m_edges = {}
|
70
|
+
#instead of diplaying several relations of the same kind between two nodes which would bloat the graph,
|
71
|
+
#we track them all and factor them on a common multiline edge label:
|
72
|
+
connex_classes = UML.collect_edges(false, local, classes, enabled_targets, m2o_edges, o2m_edges, m2m_edges)
|
73
|
+
#back links from connex classes:
|
74
|
+
connex_classes += UML.collect_edges(true, local, connex_classes - classes, classes, m2o_edges, o2m_edges, m2m_edges)
|
75
|
+
|
76
|
+
File.open('uml.dot', 'w') do |f|
|
77
|
+
f << <<-eos
|
78
|
+
digraph UMLbyOOOR {
|
79
|
+
fontname = "Helvetica"
|
80
|
+
fontsize = 11
|
81
|
+
label = "*** generated by OOOR by www.akretion.com ***"
|
82
|
+
node [
|
83
|
+
fontname = "Helvetica"
|
84
|
+
fontsize = 11
|
85
|
+
shape = "record"
|
86
|
+
fillcolor=orange
|
87
|
+
style="rounded,filled"
|
88
|
+
]
|
89
|
+
edge [
|
90
|
+
arrowhead = "none"
|
91
|
+
fontname = "Helvetica"
|
92
|
+
fontsize = 9
|
93
|
+
]
|
94
|
+
eos
|
95
|
+
|
96
|
+
#UML nodes definitions
|
97
|
+
((connex_classes - classes) + classes - [IrModel, IrModel.const_get('ir.model.fields')]).each do |model|
|
98
|
+
#f << " #{model} [ label = \"{#{model.name}#{detailed ? '|' + display_fields(model) : ''}}\" URL=\"/model/#{model.openerp_model}\"];\n"
|
99
|
+
f << "#{model}[label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"#ffffff\"><TR><TD COLSPAN=\"3\" BGCOLOR=\"#9bab96\" ALIGN=\"CENTER\">#{model.name}</TD></TR>#{display_fields(model)}</TABLE>> URL=\"/model/#{model.openerp_model}\"];\n"
|
100
|
+
end
|
101
|
+
|
102
|
+
#many2one:
|
103
|
+
f << <<-eos
|
104
|
+
edge [
|
105
|
+
headlabel = "1"
|
106
|
+
taillabel = "n"
|
107
|
+
]
|
108
|
+
eos
|
109
|
+
m2o_edges.each do |k, v|
|
110
|
+
reverse_part = v[3].size > 0 ? "\\n/#{v[3].join("\\n")}\"]\n" : "\"]\n"
|
111
|
+
f << "edge [label = \"#{v[2].join("\\n")}#{reverse_part}"
|
112
|
+
f << "#{v[0]} -> #{v[1]}\n"
|
147
113
|
end
|
148
114
|
|
115
|
+
#one2many:
|
116
|
+
f << <<-eos
|
117
|
+
edge [
|
118
|
+
headlabel = "n"
|
119
|
+
taillabel = "1"
|
120
|
+
]
|
121
|
+
eos
|
122
|
+
o2m_edges.each do |k, v|
|
123
|
+
f << "edge [label = \"#{v[3].join("\\n")}\"]\n"
|
124
|
+
f << "#{v[0]} -> #{v[1]}\n"
|
125
|
+
end
|
126
|
+
|
127
|
+
#many2many:
|
128
|
+
f << <<-eos
|
129
|
+
edge [
|
130
|
+
headlabel = "n"
|
131
|
+
taillabel = "n"
|
132
|
+
]
|
133
|
+
eos
|
134
|
+
m2m_edges.each do |k, v|
|
135
|
+
reverse_part = v[3].size > 0 ? "\\n/#{v[3].join("\\n")}\"]\n" : "\"]\n"
|
136
|
+
f << "edge [label = \"#{v[2].join("\\n")}}#{reverse_part}"
|
137
|
+
f << "#{v[0]} -> #{v[1]}\n"
|
138
|
+
end
|
139
|
+
|
140
|
+
f << "}"
|
149
141
|
end
|
142
|
+
|
143
|
+
begin
|
144
|
+
cmd_line1 = "rm uml.png"
|
145
|
+
#system(cmd_line1)
|
146
|
+
rescue
|
147
|
+
end
|
148
|
+
#cmd_line2 = "dot -Tpng uml.dot -o uml.png"
|
149
|
+
cmd_line2 = "dot -Tcmapx -ox.map -Tpng -ox.png uml.dot"
|
150
|
+
system(cmd_line2)
|
150
151
|
end
|
151
152
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
if
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
153
|
+
def self.collect_edges(is_reverse, local, classes, enabled_targets, m2o_edges, o2m_edges, m2m_edges)
|
154
|
+
connex_classes = Set.new
|
155
|
+
|
156
|
+
classes.each do |model|
|
157
|
+
model.reload_fields_definition if model.fields.empty?
|
158
|
+
|
159
|
+
#many2one:
|
160
|
+
model.many2one_associations.each do |k, field|
|
161
|
+
target = UML.get_target(is_reverse, local, enabled_targets, field, model)
|
162
|
+
if target
|
163
|
+
connex_classes.add(target)
|
164
|
+
if m2o_edges["#{model}-#{target}"]
|
165
|
+
m2o_edges["#{model}-#{target}"][2] += [k]
|
166
|
+
else
|
167
|
+
m2o_edges["#{model}-#{target}"] = [model, target, [k], []]
|
168
|
+
end
|
164
169
|
end
|
170
|
+
|
165
171
|
end
|
166
172
|
end
|
167
173
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
174
|
+
classes.each do |model|
|
175
|
+
#one2many:
|
176
|
+
model.one2many_associations.each do |k, field|
|
177
|
+
target = UML.get_target(is_reverse, local, enabled_targets, field, model)
|
178
|
+
if target
|
179
|
+
connex_classes.add(target)
|
180
|
+
if m2o_edges["#{target}-#{model}"]
|
181
|
+
m2o_edges["#{target}-#{model}"][3] += [k]
|
182
|
+
elsif o2m_edges["#{model}-#{target}"]
|
183
|
+
o2m_edges["#{model}-#{target}"][3] += [k]
|
184
|
+
else
|
185
|
+
o2m_edges["#{model}-#{target}"] = [model, target, [], [k]]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
#many2many:
|
191
|
+
model.many2many_associations.each do |k, field|
|
192
|
+
target = UML.get_target(is_reverse, local, enabled_targets, field, model)
|
193
|
+
if target
|
194
|
+
connex_classes.add(target)
|
195
|
+
if m2m_edges["#{model}-#{target}"]
|
196
|
+
m2m_edges["#{model}-#{target}"][2] += [k]
|
197
|
+
elsif m2m_edges["#{target}-#{model}"]
|
198
|
+
m2m_edges["#{target}-#{model}"][3] += [k]
|
199
|
+
else
|
200
|
+
m2m_edges["#{model}-#{target}"] = [model, target, [k], []]
|
201
|
+
end
|
179
202
|
end
|
180
203
|
end
|
181
|
-
end
|
182
204
|
|
205
|
+
end
|
206
|
+
connex_classes
|
183
207
|
end
|
184
|
-
connex_classes
|
185
|
-
end
|
186
208
|
|
187
|
-
|
209
|
+
private
|
188
210
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
211
|
+
def self.get_target(is_reverse, local, enabled_targets, field, model)
|
212
|
+
if (is_reverse && !local) || (!enabled_targets) || enabled_targets.index(field['relation'])
|
213
|
+
model.const_get(field['relation'])
|
214
|
+
else
|
215
|
+
false
|
216
|
+
end
|
194
217
|
end
|
195
218
|
end
|
196
219
|
end
|