ext_ooor 2.3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +354 -0
  4. data/Rakefile +5 -0
  5. data/bin/ooor +43 -0
  6. data/lib/ext_ooor.rb +5 -0
  7. data/lib/ext_ooor/version.rb +5 -0
  8. data/lib/generators/ooor/install_generator.rb +18 -0
  9. data/lib/generators/ooor/ooor.yml +49 -0
  10. data/lib/ooor.rb +230 -0
  11. data/lib/ooor/associations.rb +78 -0
  12. data/lib/ooor/autosave_association.rb +197 -0
  13. data/lib/ooor/base.rb +130 -0
  14. data/lib/ooor/base64.rb +20 -0
  15. data/lib/ooor/callbacks.rb +18 -0
  16. data/lib/ooor/errors.rb +120 -0
  17. data/lib/ooor/field_methods.rb +213 -0
  18. data/lib/ooor/helpers/core_helpers.rb +83 -0
  19. data/lib/ooor/locale.rb +11 -0
  20. data/lib/ooor/mini_active_resource.rb +86 -0
  21. data/lib/ooor/model_registry.rb +24 -0
  22. data/lib/ooor/model_schema.rb +25 -0
  23. data/lib/ooor/naming.rb +92 -0
  24. data/lib/ooor/nested_attributes.rb +57 -0
  25. data/lib/ooor/persistence.rb +353 -0
  26. data/lib/ooor/rack.rb +137 -0
  27. data/lib/ooor/railtie.rb +27 -0
  28. data/lib/ooor/reflection.rb +151 -0
  29. data/lib/ooor/reflection_ooor.rb +121 -0
  30. data/lib/ooor/relation.rb +204 -0
  31. data/lib/ooor/relation/finder_methods.rb +153 -0
  32. data/lib/ooor/report.rb +53 -0
  33. data/lib/ooor/serialization.rb +49 -0
  34. data/lib/ooor/services.rb +134 -0
  35. data/lib/ooor/session.rb +250 -0
  36. data/lib/ooor/session_handler.rb +66 -0
  37. data/lib/ooor/transport.rb +34 -0
  38. data/lib/ooor/transport/json_client.rb +65 -0
  39. data/lib/ooor/transport/xml_rpc_client.rb +15 -0
  40. data/lib/ooor/type_casting.rb +223 -0
  41. data/lib/ooor/version.rb +8 -0
  42. data/spec/cli_spec.rb +129 -0
  43. data/spec/helpers/test_helper.rb +11 -0
  44. data/spec/ooor_spec.rb +867 -0
  45. metadata +118 -0
@@ -0,0 +1,66 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'ooor/session'
3
+
4
+ module Ooor
5
+ autoload :SecureRandom, 'securerandom'
6
+ # The SessionHandler allows to retrieve a session with its loaded proxies to OpenERP
7
+ class SessionHandler
8
+
9
+ def noweb_session_spec(config)
10
+ "#{config[:url]}-#{config[:database]}-#{config[:username]}"
11
+ end
12
+
13
+ def retrieve_session(config, id=nil, web_session={})
14
+ id ||= SecureRandom.hex(16)
15
+ if id == :noweb
16
+ spec = noweb_session_spec(config)
17
+ else
18
+ spec = id
19
+ end
20
+
21
+ s = sessions[spec]
22
+ # reload session or create a new one if no matching session found
23
+ if config[:reload] || !s
24
+ config = Ooor.default_config.merge(config) if Ooor.default_config.is_a? Hash
25
+ Ooor::Session.new(config, web_session, id)
26
+
27
+ # found but config mismatch still
28
+ elsif noweb_session_spec(s.config) != noweb_session_spec(config)
29
+ config = Ooor.default_config.merge(config) if Ooor.default_config.is_a? Hash
30
+ Ooor::Session.new(config, web_session, id)
31
+
32
+ # matching session, update web_session of it eventually
33
+ else
34
+ s.tap {|s| s.web_session.merge!(web_session)} #TODO merge config also?
35
+ end
36
+ end
37
+
38
+ def register_session(session)
39
+ if session.config[:session_sharing]
40
+ spec = session.web_session[:session_id]
41
+ elsif session.id != :noweb
42
+ spec = session.id
43
+ else
44
+ spec = noweb_session_spec(session.config)
45
+ end
46
+ set_web_session(spec, session.web_session)
47
+ sessions[spec] = session
48
+ end
49
+
50
+ def reset!
51
+ @sessions = {}
52
+ @connections = {}
53
+ end
54
+
55
+ def get_web_session(key)
56
+ Ooor.cache.read(key)
57
+ end
58
+
59
+ def set_web_session(key, web_session)
60
+ Ooor.cache.write(key, web_session)
61
+ end
62
+
63
+ def sessions; @sessions ||= {}; end
64
+ def connections; @connections ||= {}; end
65
+ end
66
+ 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
+ Thread.current[:json_clients] ||= {}
18
+ Thread.current[:json_clients][url] ||= JsonClient.new(url, :request => { timeout: config[:rpc_timeout] || 900 })
19
+ when :xml
20
+ Thread.current[:xml_clients] ||= {}
21
+ Thread.current[: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,65 @@
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 == :db || service == :common
13
+ url = '/jsonrpc'
14
+ params = {"service"=> service, "method"=> method, "args"=> args}
15
+ elsif service == :execute
16
+ url = '/web/dataset/call_kw'
17
+ if (i = Ooor.irregular_context_position(method)) && args.size < i
18
+ kwargs = {"context"=> args[i]}
19
+ else
20
+ kwargs = {}
21
+ end
22
+ params = {"model"=>obj, "method"=> method, "kwargs"=> kwargs, "args"=>args}#, "context"=>context}
23
+ elsif service.to_s.start_with?("/") # assuming service URL is forced
24
+ url = service
25
+ params = args[0]
26
+ else
27
+ url = "/web/dataset/#{service}"
28
+ params = args[0].merge({"model"=>obj})
29
+ end # TODO reports for version > 7
30
+ oe_request(session_info, url, params, method, *args)
31
+ end
32
+
33
+ def oe_request(session_info, url, params, method, *args)
34
+ if session_info[:req_id]
35
+ session_info[:req_id] += 1
36
+ else
37
+ session_info[:req_id] = 1
38
+ end
39
+ if session_info[:sid] # required on v7 but forbidden in v8
40
+ params.merge!({"session_id" => session_info[:session_id]})
41
+ end
42
+ response = JSON.parse(post do |req|
43
+ req.headers['Cookie'] = session_info[:cookie] if session_info[:cookie]
44
+ req.url url
45
+ req.headers['Content-Type'] = 'application/json'
46
+ req.body = {"jsonrpc"=>"2.0","method"=>"call", "params" => params, "id"=>session_info[:req_id]}.to_json
47
+ end.body)
48
+ if response["error"]
49
+ faultCode = response["error"]['data']['fault_code'] || response["error"]['data']['debug']
50
+ raise OpenERPServerError.build(faultCode, response["error"]['message'], method, *args)
51
+ else
52
+ response["result"]
53
+ end
54
+ end
55
+ end
56
+
57
+ Faraday::Connection.send :include, OeAdapter
58
+
59
+ def self.new(url, options = {})
60
+ options[:ssl] = {:verify => false}
61
+ Faraday.new(url, options) # TODO use middlewares
62
+ end
63
+ end
64
+ end
65
+ 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,223 @@
1
+ # OOOR: OpenObject On Ruby
2
+ # Copyright (C) 2009-2014 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
+ BLACKLIST = %w[id write_date create_date write_ui create_ui]
12
+
13
+ module ClassMethods
14
+
15
+ def openerp_string_domain_to_ruby(string_domain) #FIXME: used? broken?
16
+ eval(string_domain.gsub('(', '[').gsub(')',']'))
17
+ end
18
+
19
+ def to_openerp_domain(domain)
20
+ if domain.is_a?(Hash)
21
+ return domain.map{|k,v| [k.to_s, '=', v]}
22
+ elsif domain == []
23
+ return []
24
+ elsif domain.is_a?(Array) && !domain.last.is_a?(Array)
25
+ return [domain]
26
+ else
27
+ return domain
28
+ end
29
+ end
30
+
31
+ def to_rails_type(type)
32
+ case type.to_sym
33
+ when :char
34
+ :string
35
+ when :binary
36
+ :file
37
+ when :many2one
38
+ :belongs_to
39
+ when :one2many
40
+ :has_many
41
+ when :many2many
42
+ :has_and_belongs_to_many
43
+ else
44
+ type.to_sym
45
+ end
46
+ end
47
+
48
+ def value_to_openerp(v)
49
+ if v == nil || v == ""
50
+ return false
51
+ elsif !v.is_a?(Integer) && !v.is_a?(Float) && v.is_a?(Numeric) && v.respond_to?(:to_f)
52
+ return v.to_f
53
+ elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:sec) && v.respond_to?(:year)#really ensure that's a datetime type
54
+ return "%d-%02d-%02d %02d:%02d:%02d" % [v.year, v.month, v.day, v.hour, v.min, v.sec]
55
+ elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:day) && v.respond_to?(:year)#really ensure that's a date type
56
+ return "%d-%02d-%02d" % [v.year, v.month, v.day]
57
+ elsif v == "false" #may happen with OOORBIT
58
+ return false
59
+ elsif v.respond_to?(:read)
60
+ return Base64.encode64(v.read())
61
+ else
62
+ v
63
+ end
64
+ end
65
+
66
+ def cast_request_to_openerp(request)
67
+ if request.is_a?(Array)
68
+ request.map { |item| cast_request_to_openerp(item) }
69
+ elsif request.is_a?(Hash)
70
+ request2 = {}
71
+ request.each do |k, v|
72
+ request2[k] = cast_request_to_openerp(v)
73
+ end
74
+ else
75
+ value_to_openerp(request)
76
+ end
77
+ end
78
+
79
+ def cast_answer_to_ruby!(answer)
80
+ def cast_map_to_ruby!(map)
81
+ map.each do |k, v|
82
+ if self.t.fields[k] && v.is_a?(String) && !v.empty?
83
+ case self.t.fields[k]['type']
84
+ when 'datetime'
85
+ map[k] = DateTime.parse(v)
86
+ when 'date'
87
+ map[k] = Date.parse(v)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ if answer.is_a?(Array)
94
+ answer.each {|item| self.cast_map_to_ruby!(item) if item.is_a? Hash}
95
+ elsif answer.is_a?(Hash)
96
+ self.cast_map_to_ruby!(answer)
97
+ else
98
+ answer
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ def sanitize_attribute(skey, value)
105
+ type = self.class.fields[skey]['type']
106
+ if type == 'boolean' && (value == 1 || value == "1")
107
+ true
108
+ elsif type == 'boolean'&& (value == 0 || value == "0")
109
+ false
110
+ elsif value == false && type != 'boolean'
111
+ nil
112
+ elsif (type == 'char' || type == 'text') && value == "" && @attributes[skey] == nil
113
+ nil
114
+ else
115
+ value
116
+ end
117
+ end
118
+
119
+ def sanitize_association(skey, value)
120
+ if value.is_a?(Ooor::Base) || value.is_a?(Array) && value.all? {|i| i.is_a?(Ooor::Base)}
121
+ value
122
+ elsif value.is_a?(Array) && !self.class.many2one_associations.keys.index(skey)
123
+ value.reject {|i| i == ''}.map {|i| i.is_a?(String) ? i.to_i : i}
124
+ elsif value.is_a?(String)
125
+ sanitize_association_as_string(skey, value)
126
+ else
127
+ value
128
+ end
129
+ end
130
+
131
+ def sanitize_association_as_string(skey, value)
132
+ if self.class.polymorphic_m2o_associations.has_key?(skey)
133
+ value
134
+ elsif self.class.many2one_associations.has_key?(skey)
135
+ sanitize_m2o_association(value)
136
+ else
137
+ value.split(",").map {|i| i.to_i}
138
+ end
139
+ end
140
+
141
+ def sanitize_m2o_association(value)
142
+ if value.blank? || value == "0"
143
+ false
144
+ else
145
+ value.to_i
146
+ end
147
+ end
148
+
149
+ def to_openerp_hash
150
+ attribute_keys, association_keys = get_changed_values
151
+ associations = {}
152
+ association_keys.each { |k| associations[k] = self.cast_association(k) }
153
+ @attributes.slice(*attribute_keys).merge(associations)
154
+ end
155
+
156
+ def get_changed_values
157
+ attribute_keys = changed.select {|k| self.class.fields.has_key?(k)} - BLACKLIST
158
+ association_keys = changed.select {|k| self.class.associations_keys.index(k)}
159
+ return attribute_keys, association_keys
160
+ end
161
+
162
+ # talk OpenERP cryptic associations API
163
+ def cast_association(k)
164
+ if self.class.one2many_associations[k]
165
+ if @loaded_associations[k]
166
+ v = @loaded_associations[k].select {|i| i.changed?}
167
+ v = @associations[k] if v.empty?
168
+ else
169
+ v = @associations[k]
170
+ end
171
+ cast_o2m_association(v)
172
+ elsif self.class.many2many_associations[k]
173
+ v = @associations[k]
174
+ [[6, false, (v || []).map {|i| i.is_a?(Base) ? i.id : i}]]
175
+ elsif self.class.many2one_associations[k]
176
+ v = @associations[k] # TODO support for nested
177
+ cast_m2o_association(v)
178
+ end
179
+ end
180
+
181
+ def cast_o2m_association(v)
182
+ v.collect do |value|
183
+ if value.is_a?(Base)
184
+ if value.new_record?
185
+ [0, 0, value.to_openerp_hash]
186
+ elsif value.marked_for_destruction?
187
+ [2, value.id]
188
+ elsif value.id
189
+ [1, value.id , value.to_openerp_hash]
190
+ end
191
+ elsif value.is_a?(Hash)
192
+ [0, 0, value]
193
+ else #use case?
194
+ [1, value, {}]
195
+ end
196
+ end
197
+ end
198
+
199
+ def cast_o2m_nested_attributes(v)
200
+ v.keys.collect do |key|
201
+ val = v[key]
202
+ if !val["_destroy"].blank?
203
+ [2, val[:id].to_i || val['id']]
204
+ elsif val[:id] || val['id']
205
+ [1, val[:id].to_i || val['id'], val]
206
+ else
207
+ [0, 0, val]
208
+ end
209
+ end
210
+ end
211
+
212
+ def cast_m2o_association(v)
213
+ if v.is_a?(Array)
214
+ v[0]
215
+ elsif v.is_a?(Base)
216
+ v.id
217
+ else
218
+ v
219
+ end
220
+ end
221
+
222
+ end
223
+ end
@@ -0,0 +1,8 @@
1
+ module Ooor
2
+ MAJOR = 2
3
+ MINOR = 3
4
+ TINY = 0
5
+ PRE = nil
6
+
7
+ VERSION = [MAJOR, MINOR, TINY].compact.join('.')
8
+ end
@@ -0,0 +1,129 @@
1
+ # Copyright (C) 2017 Akretion (<http://www.akretion.com>).
2
+ # Author: Raphaël Valyi
3
+ # Licensed under the MIT license, see MIT-LICENSE file
4
+
5
+ if ENV["CI"]
6
+ require 'coveralls'
7
+ Coveralls.wear!
8
+ end
9
+ require File.dirname(__FILE__) + '/../lib/ooor'
10
+
11
+ OOOR_URL = ENV['OOOR_URL'] || 'http://localhost:8069'
12
+ OOOR_DB_PASSWORD = ENV['OOOR_DB_PASSWORD'] || 'admin'
13
+ OOOR_USERNAME = ENV['OOOR_USERNAME'] || 'admin'
14
+ OOOR_PASSWORD = ENV['OOOR_PASSWORD'] || 'admin'
15
+ OOOR_DATABASE = ENV['OOOR_DATABASE'] || 'ooor_test'
16
+ OOOR_ODOO_VERSION = ENV['VERSION'] || '10.0'
17
+
18
+
19
+ # Note: we never set both teh password and the database to avoid invalid logins here
20
+ describe "ooor CLI" do
21
+
22
+ before do
23
+ ENV['OOOR_URL'] = nil # removed to avoid conflicting with Ooor.new tests
24
+ ENV['OOOR_DATABASE'] = nil
25
+ end
26
+
27
+ after do
28
+ ENV['OOOR_URL'] = OOOR_URL
29
+ ENV['OOOR_DATABASE'] = OOOR_DATABASE
30
+ end
31
+
32
+ describe "ooor 2.x legacy format" do
33
+ it "should parse user.mydb@myhost:8089" do
34
+ Ooor.new("user.mydb@myhost:8089")
35
+ s = Ooor.default_session
36
+ expect(s.config.username).to eq 'user'
37
+ expect(s.config.database).to eq 'mydb'
38
+ expect(s.config.url).to eq 'http://myhost:8089'
39
+ end
40
+
41
+ it "should parse user.mydb@myhost:443" do
42
+ Ooor.new("user.mydb@myhost:443")
43
+ s = Ooor.default_session
44
+ expect(s.config.username).to eq 'user'
45
+ expect(s.config.database).to eq 'mydb'
46
+ expect(s.config.url).to eq 'https://myhost:443'
47
+ end
48
+
49
+ it "should parse user.mydb@myhost:8089 -s" do
50
+ Ooor.new("user.mydb@myhost:8089 -s")
51
+ s = Ooor.default_session
52
+ expect(s.config.username).to eq 'user'
53
+ expect(s.config.database).to eq 'mydb'
54
+ expect(s.config.url).to eq 'https://myhost:8089'
55
+ end
56
+ end
57
+
58
+
59
+ describe "new connection string format" do
60
+ it "ooor://user@myhost" do
61
+ Ooor.new("ooor://user@myhost")
62
+ s = Ooor.default_session
63
+ expect(s.config.username).to eq 'user'
64
+ expect(s.config.url).to eq 'http://myhost:80'
65
+ end
66
+
67
+ it "ooor://user@myhost:8089" do
68
+ Ooor.new("ooor://user@myhost:8089")
69
+ s = Ooor.default_session
70
+ expect(s.config.username).to eq 'user'
71
+ expect(s.config.url).to eq 'http://myhost:8089'
72
+ end
73
+
74
+ it "ooor://user:secret@myhost" do
75
+ Ooor.session_handler.reset!
76
+ Ooor.new("ooor://user:secret@myhost")
77
+ s = Ooor.default_session
78
+ expect(s.config.username).to eq 'user'
79
+ expect(s.config.password).to eq 'secret'
80
+ expect(s.config.url).to eq 'http://myhost:80'
81
+ end
82
+
83
+ it "ooor://user:secret@myhost/mydb" do
84
+ Ooor.session_handler.reset!
85
+ config = Ooor.format_config("ooor://user:secret@myhost/mydb")
86
+ expect(config[:username]).to eq 'user'
87
+ expect(config[:password]).to eq 'secret'
88
+ expect(config[:database]).to eq 'mydb'
89
+ expect(config[:url]).to eq 'http://myhost:80'
90
+ end
91
+
92
+ it "ooor://user:secret@myhost:8089" do
93
+ Ooor.session_handler.reset!
94
+ Ooor.new("ooor://user:secret@myhost:8089")
95
+ s = Ooor.default_session
96
+ expect(s.config.username).to eq 'user'
97
+ expect(s.config.password).to eq 'secret'
98
+ expect(s.config.url).to eq 'http://myhost:8089'
99
+ end
100
+
101
+ it "ooor://user@myhost:8089/mydb" do
102
+ Ooor.session_handler.reset!
103
+ Ooor.new("ooor://user@myhost:8089/mydb")
104
+ s = Ooor.default_session
105
+ expect(s.config.username).to eq 'user'
106
+ expect(s.config.database).to eq 'mydb'
107
+ expect(s.config.url).to eq 'http://myhost:8089'
108
+ end
109
+
110
+ it "user:secret@myhost:8089" do
111
+ Ooor.session_handler.reset!
112
+ Ooor.new("user:secret@myhost:8089")
113
+ s = Ooor.default_session
114
+ expect(s.config.username).to eq 'user'
115
+ expect(s.config.password).to eq 'secret'
116
+ expect(s.config.url).to eq 'http://myhost:8089'
117
+ end
118
+
119
+ it "ooor://myhost.com:8089/mydb?ssl=true" do
120
+ Ooor.session_handler.reset!
121
+ Ooor.new("ooor://myhost.com:8089/mydb?ssl=true")
122
+ s = Ooor.default_session
123
+ expect(s.config.username).to eq 'admin'
124
+ expect(s.config.database).to eq 'mydb'
125
+ expect(s.config.url).to eq 'https://myhost.com:8089'
126
+ end
127
+
128
+ end
129
+ end