ooor 1.9.2 → 2.0.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.
Files changed (45) hide show
  1. data/README.md +23 -71
  2. data/Rakefile +5 -0
  3. data/bin/ooor +1 -0
  4. data/lib/ooor.rb +87 -129
  5. data/lib/ooor/associations.rb +64 -0
  6. data/lib/ooor/base.rb +218 -0
  7. data/lib/{app/models → ooor}/base64.rb +0 -0
  8. data/lib/ooor/connection.rb +37 -0
  9. data/lib/ooor/errors.rb +120 -0
  10. data/lib/ooor/field_methods.rb +153 -0
  11. data/lib/{app → ooor}/helpers/core_helpers.rb +2 -2
  12. data/lib/ooor/locale.rb +13 -0
  13. data/lib/ooor/mini_active_resource.rb +94 -0
  14. data/lib/ooor/model_registry.rb +19 -0
  15. data/lib/ooor/naming.rb +73 -0
  16. data/lib/ooor/rack.rb +114 -0
  17. data/lib/ooor/railtie.rb +41 -0
  18. data/lib/ooor/reflection.rb +537 -0
  19. data/lib/ooor/reflection_ooor.rb +92 -0
  20. data/lib/{app/models → ooor}/relation.rb +61 -22
  21. data/lib/ooor/relation/finder_methods.rb +113 -0
  22. data/lib/ooor/report.rb +53 -0
  23. data/lib/{app/models → ooor}/serialization.rb +18 -6
  24. data/lib/ooor/services.rb +133 -0
  25. data/lib/ooor/session.rb +120 -0
  26. data/lib/ooor/session_handler.rb +63 -0
  27. data/lib/ooor/transport.rb +34 -0
  28. data/lib/ooor/transport/json_client.rb +53 -0
  29. data/lib/ooor/transport/xml_rpc_client.rb +15 -0
  30. data/lib/ooor/type_casting.rb +193 -0
  31. data/lib/ooor/version.rb +8 -0
  32. data/spec/helpers/test_helper.rb +11 -0
  33. data/spec/install_nightly.sh +17 -0
  34. data/spec/ooor_spec.rb +197 -79
  35. data/spec/requirements.txt +19 -0
  36. metadata +58 -20
  37. data/lib/app/models/client_xmlrpc.rb +0 -34
  38. data/lib/app/models/open_object_resource.rb +0 -486
  39. data/lib/app/models/services.rb +0 -47
  40. data/lib/app/models/type_casting.rb +0 -134
  41. data/lib/app/models/uml.rb +0 -210
  42. data/lib/app/ui/action_window.rb +0 -96
  43. data/lib/app/ui/client_base.rb +0 -36
  44. data/lib/app/ui/form_model.rb +0 -88
  45. data/ooor.yml +0 -27
@@ -0,0 +1,120 @@
1
+ require 'ooor/services'
2
+
3
+ module Ooor
4
+ class Session < SimpleDelegator
5
+ include Transport
6
+
7
+ attr_accessor :web_session, :connection, :id
8
+
9
+ def common(); @common_service ||= CommonService.new(self); end
10
+ def db(); @db_service ||= DbService.new(self); end
11
+ def object(); @object_service ||= ObjectService.new(self); end
12
+ def report(); @report_service ||= ReportService.new(self); end
13
+
14
+ def initialize(connection, web_session, id)
15
+ super(connection)
16
+ @connection = connection
17
+ @web_session = web_session || {}
18
+ @id = id || web_session[:session_id]
19
+ end
20
+
21
+ def [](key)
22
+ @session[key]
23
+ end
24
+
25
+ def []=(key, value)
26
+ @session[key] = value
27
+ end
28
+
29
+ def global_login(options)
30
+ config.merge!(options)
31
+ load_models(config[:models], options[:reload])
32
+ end
33
+
34
+ def session_context(context={})
35
+ connection_session.merge(web_session.slice('lang', 'tz')).merge(context) # not just lang and tz?
36
+ end
37
+
38
+ def const_get(model_key, lang=nil);
39
+ if config[:aliases]
40
+ if lang && alias_data = config[:aliases][lang]
41
+ openerp_model = alias_data[model_key] || model_key
42
+ elsif alias_data = config[:aliases][connection_session['lang'] || :en_US]
43
+ openerp_model = alias_data[model_key] || model_key
44
+ else
45
+ openerp_model = model_key
46
+ end
47
+ else
48
+ openerp_model = model_key
49
+ end
50
+ define_openerp_model(model: openerp_model, scope_prefix: config[:scope_prefix], generate_constants: config[:generate_constants])
51
+ end
52
+
53
+ def[](model_key) #TODO invert: define method here and use []
54
+ const_get(model_key)
55
+ end
56
+
57
+ def load_models(model_names=config[:models], reload=config[:reload])
58
+ helper_paths.each do |dir|
59
+ Dir[dir].each { |file| require file }
60
+ end
61
+ domain = model_names ? [['model', 'in', model_names]] : []
62
+ search_domain = domain - [1]
63
+ model_ids = object.object_service(:execute, "ir.model", :search, search_domain, 0, false, false, {}, false)
64
+ models_records = object.object_service(:execute, "ir.model", :read, model_ids, ['model', 'name']) #TODO use search_read
65
+ models_records.each do |opts|
66
+ options = HashWithIndifferentAccess.new(opts.merge(scope_prefix: config[:scope_prefix], reload: reload, generate_constants: config[:generate_constants]))
67
+ define_openerp_model(options)
68
+ end
69
+ end
70
+
71
+ def set_model_template!(klass, options)
72
+ template = Ooor.model_registry.get_template(config, options[:model])
73
+ if template
74
+ klass.t = template
75
+ klass.one2many_associations.keys.each do |meth|
76
+ klass.define_nested_attributes_method(meth)
77
+ end
78
+ else
79
+ template = Ooor::ModelTemplate.new
80
+ template.openerp_model = options[:model]
81
+ template.openerp_id = options[:id]
82
+ template.description = options[:name]
83
+ template.state = options[:state]
84
+ template.many2one_associations = {}
85
+ template.one2many_associations = {}
86
+ template.many2many_associations = {}
87
+ template.polymorphic_m2o_associations = {}
88
+ template.associations_keys = []
89
+ klass.t = template
90
+ end
91
+ end
92
+
93
+ def define_openerp_model(options) #TODO param to tell if we define constants or not
94
+ if !models[options[:model]] || options[:reload]# || !scope.const_defined?(model_class_name)
95
+ scope_prefix = options[:scope_prefix]
96
+ scope = scope_prefix ? Object.const_get(scope_prefix) : Object
97
+ model_class_name = class_name_from_model_key(options[:model])
98
+ logger.debug "registering #{model_class_name}"
99
+ klass = Class.new(Base)
100
+ set_model_template!(klass, options)
101
+ klass.name = model_class_name
102
+ klass.scope_prefix = scope_prefix
103
+ klass.connection = self
104
+ if options[:generate_constants] && (options[:reload] || !scope.const_defined?(model_class_name))
105
+ scope.const_set(model_class_name, klass)
106
+ end
107
+ (Ooor.extensions[options[:model]] || []).each do |block|
108
+ klass.class_eval(&block)
109
+ end
110
+ models[options[:model]] = klass
111
+ end
112
+ models[options[:model]]
113
+ end
114
+
115
+ def models; @models ||= {}; end
116
+
117
+ def logger; Ooor.logger; end
118
+
119
+ end
120
+ end
@@ -0,0 +1,63 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'ooor/session'
3
+ require 'ooor/connection'
4
+
5
+ module Ooor
6
+ autoload :SecureRandom, 'securerandom'
7
+ class SessionHandler
8
+ def connection_spec(config)
9
+ HashWithIndifferentAccess.new(config.slice(:url, :username, :password, :database, :scope_prefix, :helper_paths)) #TODO should really password be part of it?
10
+ end
11
+
12
+ def retrieve_session(config, id=nil, web_session={})
13
+ id ||= SecureRandom.hex(16)
14
+ if config[:reload] || !s = sessions[id]
15
+ create_new_session(config, web_session, id)
16
+ else
17
+ s.tap {|s| s.web_session.merge!(web_session)} #TODO merge config also?
18
+ end
19
+ end
20
+
21
+ def create_new_session(config, web_session, id=nil)
22
+ c_spec = connection_spec(config)
23
+ if connections[c_spec]
24
+ Ooor::Session.new(connections[c_spec], web_session, id)
25
+ else
26
+ Ooor::Session.new(create_new_connection(config, c_spec), web_session, id).tap do |s|
27
+ connections[c_spec] = s.connection
28
+ end
29
+ end
30
+ end
31
+
32
+ def register_session(session)
33
+ if session.config[:session_sharing]
34
+ spec = session.web_session[:session_id]
35
+ else
36
+ spec= session.id
37
+ end
38
+ set_web_session(spec, session.web_session)
39
+ sessions[spec] = session
40
+ end
41
+
42
+ def create_new_connection(config, spec)
43
+ config = Ooor.default_config.merge(config) if Ooor.default_config.is_a? Hash
44
+ Connection.new(config)
45
+ end
46
+
47
+ def reset!
48
+ @sessions = {}
49
+ @connections = {}
50
+ end
51
+
52
+ def get_web_session(key)
53
+ Ooor.cache.read(key)
54
+ end
55
+
56
+ def set_web_session(key, web_session)
57
+ Ooor.cache.write(key, web_session)
58
+ end
59
+
60
+ def sessions; @sessions ||= {}; end
61
+ def connections; @connections ||= {}; end
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ # OOOR: OpenObject On Ruby
2
+ # Copyright (C) 2013 Akretion LTDA (<http://www.akretion.com>).
3
+ # Author: Raphaël Valyi
4
+ # Licensed under the MIT license, see MIT-LICENSE file
5
+
6
+ require 'active_support/dependencies/autoload'
7
+
8
+ module Ooor
9
+ module Transport
10
+ extend ActiveSupport::Autoload
11
+ autoload :XmlRpcClient
12
+ autoload :JsonClient
13
+
14
+ def get_client(type, url)
15
+ case type
16
+ when :json
17
+ @json_clients ||= {}
18
+ @json_clients[url] ||= JsonClient.new(url: url)#, timeout: config[:rpc_timeout] || 900) #TODO timeout doesn't work depending on Faraday versions?
19
+ when :xml
20
+ @xml_clients ||= {}
21
+ @xml_clients[url] ||= XmlRpcClient.new2(url, nil, config[:rpc_timeout] || 900)
22
+ end
23
+ end
24
+
25
+ def base_url
26
+ @base_url ||= config[:url] = "#{config[:url].gsub(/\/$/,'').chomp('/xmlrpc')}/xmlrpc"
27
+ end
28
+
29
+ def base_jsonrpc2_url
30
+ @base_jsonrpc2_url ||= config[:url].gsub(/\/$/,'').chomp('/xmlrpc')
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ require 'faraday'
2
+
3
+ module Ooor
4
+ module Transport
5
+ module JsonClient
6
+ module OeAdapter # NOTE use a middleware here?
7
+
8
+ def oe_service(session_info, service, obj, method, *args)
9
+ if service == :exec_workflow
10
+ url = '/web/dataset/exec_workflow'
11
+ params = {"model"=>obj, "id"=>args[0], "signal"=>method}
12
+ elsif service == :execute
13
+ url = '/web/dataset/call_kw'
14
+ if (i = Ooor.irregular_context_position(method)) && args.size < i
15
+ kwargs = {"context"=> args[i]}
16
+ else
17
+ kwargs = {}
18
+ end
19
+ params = {"model"=>obj, "method"=> method, "kwargs"=> kwargs, "args"=>args}#, "context"=>context}
20
+ else
21
+ url = "/web/dataset/#{service}"
22
+ params = args[0].merge({"model"=>obj})
23
+ end
24
+ oe_request(session_info, url, params, method, *args)
25
+ end
26
+
27
+ def oe_request(session_info, url, params, method, *args)
28
+ if session_info[:sid] # required on v7 but forbidden in v8
29
+ params.merge!({"session_id" => session_info[:session_id]})
30
+ end
31
+ response = JSON.parse(post do |req|
32
+ req.headers['Cookie'] = session_info[:cookie]
33
+ req.url url
34
+ req.headers['Content-Type'] = 'application/json'
35
+ req.body = {"jsonrpc"=>"2.0","method"=>"call", "params" => params, "id"=>"r42"}.to_json
36
+ end.body)
37
+ if response["error"]
38
+ faultCode = response["error"]['data']['fault_code'] || response["error"]['data']['debug']
39
+ raise OpenERPServerError.build(faultCode, response["error"]['message'], method, *args)
40
+ else
41
+ response["result"]
42
+ end
43
+ end
44
+ end
45
+
46
+ Faraday::Connection.send :include, OeAdapter
47
+
48
+ def self.new(url = nil, options = nil)
49
+ Faraday.new(url, options) # TODO use middlewares
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ require 'xmlrpc/client'
2
+
3
+ module Ooor
4
+ module Transport
5
+ class XmlRpcClient < XMLRPC::Client
6
+ def call2(method, *args)
7
+ request = create().methodCall(method, *args)
8
+ data = (["<?xml version='1.0' encoding='UTF-8'?>\n"] + do_rpc(request, false).lines.to_a[1..-1]).join #encoding is not defined by OpenERP and can lead to bug with Ruby 1.9
9
+ parser().parseMethodResponse(data)
10
+ rescue RuntimeError => e
11
+ raise OpenERPServerError.create_from_trace(e, method, *args)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,193 @@
1
+ # OOOR: OpenObject On Ruby
2
+ # Copyright (C) 2009-2012 Akretion LTDA (<http://www.akretion.com>).
3
+ # Author: Raphaël Valyi
4
+ # Licensed under the MIT license, see MIT-LICENSE file
5
+
6
+ module Ooor
7
+ module TypeCasting
8
+ extend ActiveSupport::Concern
9
+
10
+ OPERATORS = ["=", "!=", "<=", "<", ">", ">=", "=?", "=like", "=ilike", "like", "not like", "ilike", "not ilike", "in", "not in", "child_of"]
11
+
12
+ module ClassMethods
13
+
14
+ def openerp_string_domain_to_ruby(string_domain) #FIXME: used? broken?
15
+ eval(string_domain.gsub('(', '[').gsub(')',']'))
16
+ end
17
+
18
+ def to_openerp_domain(domain)
19
+ if domain.is_a?(Hash)
20
+ return domain.map{|k,v| [k.to_s, '=', v]}
21
+ elsif domain == []
22
+ return []
23
+ elsif domain.is_a?(Array) && !domain.last.is_a?(Array)
24
+ return [domain]
25
+ else
26
+ return domain
27
+ end
28
+ end
29
+
30
+ def to_rails_type(type)
31
+ case type.to_sym
32
+ when :char
33
+ :string
34
+ when :binary
35
+ :file
36
+ when :many2one
37
+ :belongs_to
38
+ when :one2many
39
+ :has_many
40
+ when :many2many
41
+ :has_and_belongs_to_many
42
+ else
43
+ type.to_sym
44
+ end
45
+ end
46
+
47
+ def value_to_openerp(v)
48
+ if v == nil || v == ""
49
+ return false
50
+ elsif !v.is_a?(Integer) && !v.is_a?(Float) && v.is_a?(Numeric) && v.respond_to?(:to_f)
51
+ return v.to_f
52
+ elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:sec) && v.respond_to?(:year)#really ensure that's a datetime type
53
+ return "%d-%02d-%02d %02d:%02d:%02d" % [v.year, v.month, v.day, v.hour, v.min, v.sec]
54
+ elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:day) && v.respond_to?(:year)#really ensure that's a date type
55
+ return "%d-%02d-%02d" % [v.year, v.month, v.day]
56
+ elsif v == "false" #may happen with OOORBIT
57
+ return false
58
+ elsif v.respond_to?(:read)
59
+ return Base64.encode64(v.read())
60
+ else
61
+ v
62
+ end
63
+ end
64
+
65
+ def cast_request_to_openerp(request)
66
+ if request.is_a?(Array)
67
+ request.map { |item| cast_request_to_openerp(item) }
68
+ elsif request.is_a?(Hash)
69
+ request2 = {}
70
+ request.each do |k, v|
71
+
72
+ if k.to_s.end_with?("_attributes")
73
+ attrs = []
74
+ if v.is_a?(Hash)
75
+ v.each do |key, val|
76
+ if !val["_destroy"].empty?
77
+ attrs << [2, val[:id] || val['id']]
78
+ elsif val[:id] || val['id']
79
+ attrs << [1, val[:id] || val['id'], cast_request_to_openerp(val)]
80
+ else
81
+ attrs << [0, 0, cast_request_to_openerp(val)]
82
+ end
83
+ end
84
+ end
85
+
86
+ request2[k.to_s.gsub("_attributes", "")] = attrs
87
+ else
88
+ request2[k] = cast_request_to_openerp(v)
89
+ end
90
+ end
91
+ request2
92
+
93
+ else
94
+ value_to_openerp(request)
95
+ end
96
+ end
97
+
98
+ def cast_answer_to_ruby!(answer)
99
+ def cast_map_to_ruby!(map)
100
+ map.each do |k, v|
101
+ if self.t.fields[k] && v.is_a?(String) && !v.empty?
102
+ case self.t.fields[k]['type']
103
+ when 'datetime'
104
+ map[k] = DateTime.parse(v)
105
+ when 'date'
106
+ map[k] = Date.parse(v)
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ if answer.is_a?(Array)
113
+ answer.each {|item| self.cast_map_to_ruby!(item) if item.is_a? Hash}
114
+ elsif answer.is_a?(Hash)
115
+ self.cast_map_to_ruby!(answer)
116
+ else
117
+ answer
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ def to_openerp_hash(attributes=@attributes, associations=@associations)
124
+ associations = cast_relations_to_openerp(associations)
125
+ blacklist = %w[id write_date create_date write_ui create_ui]
126
+ r = {}
127
+ attributes.reject {|k, v| blacklist.index(k)}.merge(associations).each do |k, v|
128
+ if k.end_with?("_id") && !self.class.associations_keys.index(k) && self.class.associations_keys.index(k.gsub(/_id$/, ""))
129
+ r[k.gsub(/_id$/, "")] = v && v.to_i || v
130
+ else
131
+ r[k] = v
132
+ end
133
+ end
134
+ r
135
+ end
136
+
137
+ def cast_relations_to_openerp(associations=@associations)
138
+ associations2 = {}
139
+ associations.each do |k, v|
140
+ if k.match(/_ids$/) && !self.class.associations_keys.index(k) && self.class.associations_keys.index(rel = k.gsub(/_ids$/, ""))
141
+ if v.is_a? Array
142
+ v.reject! {|i| i == ''}.map! {|i| i.to_i}
143
+ end
144
+ associations2[rel] = v
145
+ elsif v.is_a?(Array) && (v.size == 0 or v[1].is_a?(String)) #reject non assigned many2one or empty list
146
+ next
147
+ else
148
+ if k.end_with?("_ids") && v.is_a?(String)
149
+ v = v.split(",").map{|i| i.to_i}
150
+ end
151
+ associations2[k] = v
152
+ end
153
+ end
154
+
155
+ associations2.each do |k, v| #see OpenERP awkward associations API
156
+ #already casted, possibly before server error!
157
+ next if (v.is_a?(Array) && v.size == 1 && v[0].is_a?(Array)) \
158
+ || self.class.many2one_associations[k] \
159
+ || !v.is_a?(Array)
160
+ new_rel = self.cast_relation(k, v, self.class.one2many_associations, self.class.many2many_associations)
161
+ if new_rel #matches a known o2m or m2m
162
+ associations2[k] = new_rel
163
+ else
164
+ self.class.many2one_associations.each do |k2, field| #try to cast the association to an inherited o2m or m2m:
165
+ linked_class = self.class.const_get(field['relation'])
166
+ new_rel = self.cast_relation(k, v, linked_class.one2many_associations, linked_class.many2many_associations)
167
+ associations2[k] = new_rel and break if new_rel
168
+ end
169
+ end
170
+ end
171
+ associations2
172
+ end
173
+
174
+ def cast_relation(k, v, one2many_associations, many2many_associations)
175
+ if one2many_associations[k]
176
+ return v.collect do |value|
177
+ if value.is_a?(Base) #on the fly creation as in the GTK client
178
+ [0, 0, value.to_openerp_hash]
179
+ else
180
+ if value.is_a?(Hash)
181
+ [0, 0, value]
182
+ else
183
+ [1, value, {}]
184
+ end
185
+ end
186
+ end
187
+ elsif many2many_associations[k]
188
+ return v = [[6, 0, v]]
189
+ end
190
+ end
191
+
192
+ end
193
+ end