ooor 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -3,10 +3,10 @@ OOOR - OpenObject On Rails
3
3
 
4
4
  <table>
5
5
  <tr>
6
- <td><a href="http://github.com/rvalyi/ooor" title="OOOR - OpenObject On Rails"><img src="http://sites.google.com/site/assetserversite/_/rsrc/1257126460164/home/ooor_s.jpg" width="159px" height="124px" /></a></td>
6
+ <td width="159px"><a href="http://github.com/rvalyi/ooor" title="OOOR - OpenObject On Rails"><img src="http://akretion.s3.amazonaws.com/assets/ooor_m.jpg" width="159px" height="124px" /></a></td>
7
7
  <td><b>BY</b></td>
8
- <td><a href="http://www.akretion.com" title="Akretion - open source to spin the world"><img src="http://sites.google.com/a/akretion.com/assetserver/_/rsrc/1276813508598/home/logo.png?height=154&width=320" width="320px" height="154px" /></a></td>
9
- <td>
8
+ <td width="320px"><a href="http://www.akretion.com" title="Akretion - open source to spin the world"><img src="http://akretion.s3.amazonaws.com/assets/logo.png" width="320px" height="154px" /></a></td>
9
+ <td width="285px">
10
10
  OOOR stands for OpenObject On Rails. OpenObject is the RAD framework behind OpenERP,
11
11
  the ERP that doesn't hurt, just like Rails is "web development that doesn't hurt".
12
12
  So OOOR exposes seamlessly your OpenOpbject application, to your custom Rails application.
@@ -85,6 +85,8 @@ Trying it simply
85
85
  If you have Java 1.6+ installed, then the easiest way to tryout OOOR might be to download the [TerminatOOOR zip](http://github.com/rvalyi/terminatooor/downloads)
86
86
  and double-click on jruby-ooor.jar or launch it by command line with java -jar jruby-ooor.jar: it will launch an OOOR console with helpful auto-completion (hit 'tab') on OpenERP business objects.
87
87
 
88
+ You can read [an introduction to OOOR on Akretion's blog.](http://www.akretion.com/en/blog/2010/01/18/introducing-ooor---openobject-on-rails-drivingrequesting-your-openerp-became-a-child-play/)
89
+
88
90
 
89
91
 
90
92
  Installation
@@ -105,7 +107,7 @@ In all case, you first need to install the ooor gem:
105
107
  Let's test OOOR in an irb console (irb command):
106
108
  $ require 'rubygems'
107
109
  $ require 'ooor'
108
- $ Ooor.new({:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
110
+ $ Ooor.new(:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin')
109
111
  This should load all your OpenERP models into Ruby proxy Activeresource objects. Of course there are option to load only some models.
110
112
  Let's try to retrieve the user with id 1:
111
113
  $ ResUsers.find(1)
@@ -115,27 +117,7 @@ Let's try to retrieve the user with id 1:
115
117
 
116
118
  ### (J)Ruby on Rails application:
117
119
 
118
- we assume you created a working Rails application, in your config/environment.rb
119
- Inside the Rails::Initializer.run do |config| statement, paste the following gem dependency:
120
-
121
- $ config.gem "ooor"
122
-
123
- Now, you should also create a ooor.yml config file in your config directory
124
- You can copy/paste [the default ooor.yml from the OOOR gem](http://github.com/rvalyi/ooor/blob/master/ooor.yml)
125
- and then adapt it to your OpenERP server environment.
126
- If you set the 'bootstrap' parameter to true, OpenERP models will be loaded at the Rails startup.
127
- That the easiest option to get started while you might not want that in production.
128
-
129
- Then just start your Rails application, your OpenERP models will be loaded as you'll see in the Rails log.
130
- You can then use all the OOOR API upon all loaded OpenERP models in your regular Rails code (see API usage section).
131
- A good way to start playing with OOOR is inside the console, using:
132
- $ ruby script/console #or jruby script/console on JRuby of course
133
-
134
- Note: when boostraping Ooor in a Rails application, the default Ooor instance is stored in the Ooor.default_ooor variable.
135
- So for instance you can know all loaded models doing Ooor.default_ooor variable.loaded_models; this is used by [OooREST](http://github.com/rvalyi/ooorest) to register all the REST controllers.
136
-
137
- Enabling REST HTTP routes to your OpenERP models:
138
- The REST Controller layer of OOOR has been moved as a thin separate gem called [OooREST](http://github.com/rvalyi/ooorest).
120
+ Please read details [https://github.com/rvalyi/ooor/wiki/(J)Ruby-on-Rails-application](here)
139
121
 
140
122
 
141
123
  API usage
@@ -243,7 +225,8 @@ On Change methods:
243
225
 
244
226
  Note: currently OOOR doesn't deal with the View layer, or has a very limited support for forms for the wizards.
245
227
  So, it's not possible so far for OOOR to know an on_change signature. Because of this, the on_change syntax is bit awkward
246
- as you will see: you need to explicitely tell the on_change name, the parameter name that changed, the new value and finally
228
+ as you will see (fortunately OpenERP SA announced they will fix that on_change API in subsequent v6 OpenERP releases):
229
+ you need to explicitely tell the on_change name, the parameter name that changed, the new value and finally
247
230
  enfore the on_change syntax (looking at the OpenERP model code or view or XML/RPC logs will help you to find out). But
248
231
  ultimately it works:
249
232
 
@@ -340,47 +323,4 @@ In this case [CampToCamp](http://www.camptocamp.com/) used the famous [Cucumber
340
323
  FAQ
341
324
  ------------
342
325
 
343
- ### How do I know which object or OpenERP webservice I should Invoke from OOOR?
344
-
345
- An easy is to use your GTK client and start it with the -l debug_rpc (or alternatively -l debug_rpc_answer) option.
346
- For non *nix users, you can alternatively start your server with the --log-level=debug_rpc option (you can also set this option in your hidden OpenERP server config file in your user directory).
347
- Then create indents in the log before doing some action and watch your logs carefully. OOOR will allow you to do the same easily from Ruby/Rails.
348
-
349
- ### How can I load/reload my OpenERP models into my Ruby application?
350
-
351
- You can load/reload your models at any time (even in console), creating a new Ooor instance that will override the class definitions:
352
- $ Ooor.new({:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
353
- or using a config YAML file instead:
354
- $ Ooor.new("config/ooor.yml")
355
-
356
- ### Do I need to load all the OpenERP models in my Ruby application?
357
-
358
- You can load only some OpenERP models (not all), which is faster and better in term of memory/security:
359
- $ Ooor.reload!({:models => [res.partner, product.template, product.product], :url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
360
-
361
- ### Isn't OOOR slow?
362
-
363
- You might think that proxying a Python based server through a Rails app using XML/RPC would be dog slow. Well, not too much actually.
364
- I did some load testing using a Centrino Duo laptop (dual core at 1.6 GHz), and I was able to approach 10 requests/sec, to display some OpenERP resource as XML through HTTP.
365
- That should be enough for what it's meant too. Heavily loaded application might cache things at the Rails level if that's too slow, but I don't think so.
366
-
367
- Also notice that using JRuby I could serve some 20% faster.
368
-
369
- ### JRuby compatibility
370
-
371
- Yes Ooor is fully JRuby compatible. It's even somewhat 20% faster using Java6 + last JRuby.
372
- This might be espcially interresting if you plan to mix Java libraries with OpenERP in the same web appication.
373
-
374
- ### Can I extend the OOOR models?
375
-
376
- Yes you can perfectly do that. Basically an OpenERP model get a basic ActiveResource model. For instance: product.product is mapped to the ProductProduct ActiveResource model.
377
- But using the Ruby open classes features, you can asbolutely re-open the ProductProduct class and add features of your own.
378
- In you app/model directory, create a product_product.rb file with inside, the redéfinition of ProductProduct, for instance:
379
-
380
- $ class ProductProduct < OpenObjectResource
381
- $ def foo
382
- $ "bar"
383
- $ end
384
- $ end
385
-
386
- Now a ProductProduct resource got a method foo, returning "bar".
326
+ Please read the [FAQ here](https://github.com/rvalyi/ooor/wiki/FAQ)
@@ -1,5 +1,5 @@
1
1
  # OOOR: Open Object On Rails
2
- # Copyright (C) 2009-2010 Akretion LTDA (<http://www.akretion.com>).
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
@@ -1,5 +1,5 @@
1
1
  # OOOR: Open Object On Rails
2
- # Copyright (C) 2009-2010 Akretion LTDA (<http://www.akretion.com>).
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
@@ -16,27 +16,28 @@
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
 
18
18
  #proxies all 'common' class of OpenERP server/bin/service/web_service.py properly
19
- module CommonService
20
- def global_login(user, password)
21
- @config[:username] = user
22
- @config[:password] = password
23
- client = OpenObjectResource.client(@base_url + "/common")
24
- @config[:user_id] = OpenObjectResource.try_with_pretty_error_log { client.call("login", @config[:database], user, password) }
25
- rescue Exception => error
26
- @logger.error """login to OpenERP server failed:
27
- #{error.inspect}
28
- Are your sure the server is started? Are your login parameters correct? Can this server ping the OpenERP server?
29
- login XML/RPC url was #{@config[:url].gsub(/\/$/,'') + "/common"}"""
30
- raise
31
- end
19
+ module Ooor
20
+ module CommonService
21
+ def global_login(user, password)
22
+ @config[:username] = user
23
+ @config[:password] = password
24
+ @config[:user_id] = OpenObjectResource.client(@base_url + "/common").call("login", @config[:database], user, password)
25
+ rescue Exception => error
26
+ @logger.error """login to OpenERP server failed:
27
+ #{error.inspect}
28
+ Are your sure the server is started? Are your login parameters correct? Can this server ping the OpenERP server?
29
+ login XML/RPC url was #{@config[:url].gsub(/\/$/,'') + "/common"}"""
30
+ raise
31
+ end
32
32
 
33
- def login(user, password); global_login(user, password); end
33
+ def login(user, password); global_login(user, password); end
34
34
 
35
- #we generate methods handles for use in auto-completion tools such as jirb_swing
36
- [:ir_get, :ir_set, :ir_del, :about, :logout, :timezone_get, :get_available_updates, :get_migration_scripts, :get_server_environment, :login_message, :check_connectivity].each do |meth|
37
- self.instance_eval do
38
- define_method meth do |*args|
39
- OpenObjectResource.try_with_pretty_error_log { OpenObjectResource.client(@base_url + "/common").call(meth.to_s, *args) }
35
+ #we generate methods handles for use in auto-completion tools such as jirb_swing
36
+ [:ir_get, :ir_set, :ir_del, :about, :logout, :timezone_get, :get_available_updates, :get_migration_scripts, :get_server_environment, :login_message, :check_connectivity].each do |meth|
37
+ self.instance_eval do
38
+ define_method meth do |*args|
39
+ OpenObjectResource.client(@base_url + "/common").call(meth.to_s, *args)
40
+ end
40
41
  end
41
42
  end
42
43
  end
@@ -1,5 +1,5 @@
1
1
  # OOOR: Open Object On Rails
2
- # Copyright (C) 2009-2010 Akretion LTDA (<http://www.akretion.com>).
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
@@ -16,28 +16,30 @@
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
 
18
18
  #proxies all 'db' class of OpenERP server/bin/service/web_service.py properly
19
- module DbService
20
- def create(password=@config[:db_password], db_name='ooor_db', demo=true, lang='en_US', user_password=@config[:password] || 'admin')
21
- process_id = OpenObjectResource.try_with_pretty_error_log { OpenObjectResource.client(@base_url + "/db").call("create", password, db_name, demo, lang, user_password) }
22
- @config[:database] = db_name
23
- @config[:username] = 'admin'
24
- @config[:passowrd] = user_password
25
- while get_progress('admin', process_id) == [0, []]
26
- @logger.info "..."
27
- sleep(0.5)
19
+ module Ooor
20
+ module DbService
21
+ def create(password=@config[:db_password], db_name='ooor_db', demo=true, lang='en_US', user_password=@config[:password] || 'admin')
22
+ process_id = OpenObjectResource.client(@base_url + "/db").call("create", password, db_name, demo, lang, user_password)
23
+ @config[:database] = db_name
24
+ @config[:username] = 'admin'
25
+ @config[:passowrd] = user_password
26
+ while get_progress('admin', process_id) == [0, []]
27
+ @logger.info "..."
28
+ sleep(0.5)
29
+ end
30
+ load_models()
28
31
  end
29
- load_models()
30
- end
31
32
 
32
- def drop(password=@config[:db_password], db_name='ooor_db')
33
- OpenObjectResource.try_with_pretty_error_log { OpenObjectResource.client(@base_url + "/db").call("drop", password, db_name) }
34
- end
33
+ def drop(password=@config[:db_password], db_name='ooor_db')
34
+ OpenObjectResource.client(@base_url + "/db").call("drop", password, db_name)
35
+ end
35
36
 
36
- #we generate methods handles for use in auto-completion tools such as jirb_swing
37
- [:get_progress, :dump, :restore, :rename, :db_exist, :list, :change_admin_password, :list_lang, :server_version, :migrate_databases].each do |meth|
38
- self.instance_eval do
39
- define_method meth do |*args|
40
- OpenObjectResource.try_with_pretty_error_log { OpenObjectResource.client(@base_url + "/db").call(meth.to_s, *args) }
37
+ #we generate methods handles for use in auto-completion tools such as jirb_swing
38
+ [:get_progress, :dump, :restore, :rename, :db_exist, :list, :change_admin_password, :list_lang, :server_version, :migrate_databases].each do |meth|
39
+ self.instance_eval do
40
+ define_method meth do |*args|
41
+ OpenObjectResource.client(@base_url + "/db").call(meth.to_s, *args)
42
+ end
41
43
  end
42
44
  end
43
45
  end
@@ -0,0 +1,41 @@
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
+ require 'xmlrpc/client'
19
+
20
+ module Ooor
21
+ class OOORClient < XMLRPC::Client
22
+ def call2(method, *args)
23
+ request = create().methodCall(method, *args)
24
+ 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
25
+ parser().parseMethodResponse(data)
26
+ rescue RuntimeError => e
27
+ begin
28
+ #extracts the eventual error log from OpenERP response as OpenERP doesn't enforce carefully*
29
+ #the XML/RPC spec, see https://bugs.launchpad.net/openerp/+bug/257581
30
+ openerp_error_hash = eval("#{ e }".gsub("wrong fault-structure: ", ""))
31
+ rescue SyntaxError
32
+ raise e
33
+ end
34
+ if openerp_error_hash.is_a? Hash
35
+ raise RuntimeError.new "\n\n*********** OpenERP Server ERROR ***********\n#{openerp_error_hash["faultCode"]}\n#{openerp_error_hash["faultString"]}********************************************\n."
36
+ else
37
+ raise e
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  # OOOR: Open Object On Rails
2
- # Copyright (C) 2009-2010 Akretion LTDA (<http://www.akretion.com>).
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
@@ -15,490 +15,404 @@
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
17
 
18
- require 'xmlrpc/client'
19
18
  require 'rubygems'
20
19
  require 'active_resource'
21
20
  require 'app/ui/form_model'
22
21
  require 'app/models/uml'
22
+ require 'app/models/ooor_client'
23
+ require 'app/models/type_casting'
24
+ require 'app/models/relation'
25
+
26
+ module Ooor
27
+ class OpenObjectResource < ActiveResource::Base
28
+ #PREDEFINED_INHERITS = {'product.product' => 'product_tmpl_id'}
29
+ #include ActiveModel::Validations
30
+ include UML
31
+ include TypeCasting
32
+
33
+ # ******************** class methods ********************
34
+ class << self
35
+
36
+ cattr_accessor :logger
37
+ attr_accessor :openerp_id, :info, :access_ids, :name, :openerp_model, :field_ids, :state, #model class attributes associated to the OpenERP ir.model
38
+ :fields, :fields_defined, :many2one_associations, :one2many_associations, :many2many_associations, :polymorphic_m2o_associations, :associations_keys,
39
+ :database, :user_id, :scope_prefix, :ooor, :association
40
+
41
+ def class_name_from_model_key(model_key=self.openerp_model)
42
+ model_key.split('.').collect {|name_part| name_part.capitalize}.join
43
+ end
23
44
 
24
- class OpenObjectResource < ActiveResource::Base
25
- include UML
26
-
27
- # ******************** class methods ********************
28
- class << self
29
-
30
- cattr_accessor :logger
31
- attr_accessor :openerp_id, :info, :access_ids, :name, :openerp_model, :field_ids, :state, #model class attributes assotiated to the OpenERP ir.model
32
- :fields, :fields_defined, :many2one_relations, :one2many_relations, :many2many_relations, :polymorphic_m2o_relations, :relations_keys,
33
- :database, :user_id, :scope_prefix, :ooor
34
-
35
- def class_name_from_model_key(model_key=self.openerp_model)
36
- model_key.split('.').collect {|name_part| name_part.capitalize}.join
37
- end
38
-
39
- #similar to Object#const_get but for OpenERP model key
40
- def const_get(model_key)
41
- klass_name = class_name_from_model_key(model_key)
42
- klass = (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_defined?(klass_name) ? (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_get(klass_name) : @ooor.define_openerp_model({'model' => model_key}, self.scope_prefix)
43
- klass.reload_fields_definition()
44
- klass
45
- end
45
+ #similar to Object#const_get but for OpenERP model key
46
+ def const_get(model_key)
47
+ klass_name = class_name_from_model_key(model_key)
48
+ klass = (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_defined?(klass_name) ? (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_get(klass_name) : @ooor.define_openerp_model({'model' => model_key}, self.scope_prefix)
49
+ klass.reload_fields_definition()
50
+ klass
51
+ end
46
52
 
47
- def create(attributes = {}, context={}, default_get_list=false, reload=true)
48
- self.new(attributes, default_get_list, context).tap { |resource| resource.save(context, reload) }
49
- end
53
+ def create(attributes = {}, context={}, default_get_list=false, reload=true)
54
+ self.new(attributes, default_get_list, context).tap { |resource| resource.save(context, reload) }
55
+ end
50
56
 
51
- def reload_fields_definition(force = false)
52
- if force or not @fields_defined
53
- @fields_defined = true
54
- @fields = {}
55
- rpc_execute("fields_get").each do |k, field|
56
- case field['type']
57
- when 'many2one'
58
- @many2one_relations[k] = field
59
- when 'one2many'
60
- @one2many_relations[k] = field
61
- when 'many2many'
62
- @many2many_relations[k] = field
63
- when 'reference'
64
- @polymorphic_m2o_relations[k] = field
65
- else
66
- @fields[k] = field
57
+ def reload_fields_definition(force = false)
58
+ if force or not @fields_defined
59
+ @fields_defined = true
60
+ @fields = {}
61
+ rpc_execute("fields_get").each do |k, field|
62
+ case field['type']
63
+ when 'many2one'
64
+ @many2one_associations[k] = field
65
+ when 'one2many'
66
+ @one2many_associations[k] = field
67
+ when 'many2many'
68
+ @many2many_associations[k] = field
69
+ when 'reference'
70
+ @polymorphic_m2o_associations[k] = field
71
+ else
72
+ # if ['integer', 'int8'].index(field['type'])
73
+ # self.send :validates_numericality_of, k, :only_integer => true
74
+ # elsif field['type'] == 'float'
75
+ # self.send :validates_numericality_of, k
76
+ # elsif field['type'] == 'char'
77
+ # self.send :validates_length_of, k, :maximum => field['size'] || 128
78
+ # end
79
+ @fields[k] = field if field['name'] != 'id'
80
+ end
81
+ # if field["required"]
82
+ # if field['type'] == 'many2one'
83
+ # next if PREDEFINED_INHERITS[self.openerp_model] == k
84
+ # end
85
+ # self.send :validates_presence_of, k
86
+ # end
67
87
  end
68
- end
69
- @relations_keys = @many2one_relations.keys + @one2many_relations.keys + @many2many_relations.keys + @polymorphic_m2o_relations.keys
70
- (@fields.keys + @relations_keys).each do |meth| #generates method handlers for autompletion tools such as jirb_swing
71
- unless self.respond_to?(meth)
72
- self.instance_eval do
73
- define_method meth do |*args|
74
- self.send :method_missing, *[meth, *args]
88
+ @associations_keys = @many2one_associations.keys + @one2many_associations.keys + @many2many_associations.keys + @polymorphic_m2o_associations.keys
89
+ (@fields.keys + @associations_keys).each do |meth| #generates method handlers for auto-completion tools such as jirb_swing
90
+ unless self.respond_to?(meth)
91
+ self.instance_eval do
92
+ define_method meth do |*args|
93
+ self.send :method_missing, *[meth, *args]
94
+ end
75
95
  end
76
96
  end
77
97
  end
98
+ logger.debug "#{fields.size} fields loaded in model #{self.name}"
78
99
  end
79
- logger.info "#{fields.size} fields loaded in model #{self.class}"
80
100
  end
81
- end
82
101
 
83
- # ******************** remote communication ********************
102
+ # ******************** remote communication ********************
84
103
 
85
- #OpenERP search method
86
- def search(domain=[], offset=0, limit=false, order=false, context={}, count=false)
87
- rpc_execute('search', domain, offset, limit, order, context, count)
88
- end
89
-
90
- def client(url)
91
- @clients ||= {}
92
- @clients[url] ||= XMLRPC::Client.new2(url, nil, 900)
93
- end
94
-
95
- #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
96
- def rpc_execute(method, *args)
97
- rpc_execute_with_object(@openerp_model, method, *args)
98
- end
99
-
100
- def rpc_execute_with_object(object, method, *args)
101
- rpc_execute_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, method, *args)
102
- end
103
-
104
- #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
105
- def rpc_execute_with_all(db, uid, pass, obj, method, *args)
106
- clean_request_args!(args)
107
- logger.debug "rpc_execute_with_all: rpc_method: 'execute', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.inspect}, obj: #{obj.inspect}, method: #{method}, *args: #{args.inspect}"
108
- try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("execute", db, uid, pass, obj, method, *args)) }
109
- end
110
-
111
- #corresponding method for OpenERP osv.exec_workflow(self, db, uid, obj, method, *args)
112
- def rpc_exec_workflow(action, *args)
113
- rpc_exec_workflow_with_object(@openerp_model, action, *args)
114
- end
104
+ #OpenERP search method
105
+ def search(domain=[], offset=0, limit=false, order=false, context={}, count=false)
106
+ rpc_execute('search', to_openerp_domain(domain), offset, limit, order, context, count)
107
+ end
108
+
109
+ def relation; @relation ||= Relation.new(self); end
110
+ def where(opts, *rest); relation.where(opts, *rest); end
111
+ def all(*args); relation.all(*args); end
112
+ def limit(value); relation.limit(value); end
113
+ def order(value); relation.order(value); end
114
+ def offset(value); relation.offset(value); end
115
+
116
+ def client(url)
117
+ @clients ||= {}
118
+ @clients[url] ||= OOORClient.new2(url, nil, 900)
119
+ end
115
120
 
116
- def rpc_exec_workflow_with_object(object, action, *args)
117
- rpc_exec_workflow_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, action, *args)
118
- end
121
+ #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
122
+ def rpc_execute(method, *args)
123
+ rpc_execute_with_object(@openerp_model, method, *args)
124
+ end
119
125
 
120
- def rpc_exec_workflow_with_all(db, uid, pass, obj, action, *args)
121
- clean_request_args!(args)
122
- logger.debug "rpc_execute_with_all: rpc_method: 'exec_workflow', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.inspect}, obj: #{obj.inspect}, action: #{action}, *args: #{args.inspect}"
123
- try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("exec_workflow", db, uid, pass, obj, action, *args)) }
124
- end
126
+ def rpc_execute_with_object(object, method, *args)
127
+ rpc_execute_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, method, *args)
128
+ end
125
129
 
126
- def old_wizard_step(wizard_name, ids, step='init', wizard_id=nil, form={}, context={}, report_type='pdf')
127
- context = @ooor.global_context.merge(context)
128
- cast_request_to_openerp!(form)
129
- unless wizard_id
130
- logger.debug "rpc_execute_with_all: rpc_method: 'create old_wizard_step' #{wizard_name}"
131
- wizard_id = try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("create", @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], wizard_name)) }
130
+ #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
131
+ def rpc_execute_with_all(db, uid, pass, obj, method, *args)
132
+ clean_request_args!(args)
133
+ reload_fields_definition()
134
+ logger.debug "OOOR RPC: rpc_method: 'execute', db: #{db}, uid: #{uid}, pass: #, obj: #{obj}, method: #{method}, *args: #{args.inspect}"
135
+ cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("execute", db, uid, pass, obj, method, *args))
132
136
  end
133
- params = {'model' => @openerp_model, 'form' => form, 'report_type' => report_type}
134
- params.merge!({'id' => ids[0], 'ids' => ids}) if ids
135
- logger.debug "rpc_execute_with_all: rpc_method: 'execute old_wizard_step' #{wizard_id}, #{params.inspect}, #{step}, #{context}"
136
- [wizard_id, try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("execute", @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], wizard_id, params, step, context)) }]
137
- end
138
137
 
139
- #grab the eventual error log from OpenERP response as OpenERP doesn't enforce carefuly
140
- #the XML/RPC spec, see https://bugs.launchpad.net/openerp/+bug/257581
141
- def try_with_pretty_error_log
142
- yield
143
- rescue RuntimeError => e
144
- begin
145
- openerp_error_hash = eval("#{ e }".gsub("wrong fault-structure: ", ""))
146
- rescue SyntaxError
147
- raise e
148
- end
149
- raise e unless openerp_error_hash.is_a? Hash
150
- logger.error "*********** OpenERP Server ERROR:\n#{openerp_error_hash["faultString"]}***********"
151
- raise RuntimeError.new('OpenERP server error')
152
- end
138
+ #corresponding method for OpenERP osv.exec_workflow(self, db, uid, obj, method, *args)
139
+ def rpc_exec_workflow(action, *args)
140
+ rpc_exec_workflow_with_object(@openerp_model, action, *args)
141
+ end
153
142
 
154
- def clean_request_args!(args)
155
- if args[-1].is_a? Hash
156
- args[-1] = @ooor.global_context.merge(args[-1])
157
- elsif args.is_a?(Array)
158
- args += [@ooor.global_context]
143
+ def rpc_exec_workflow_with_object(object, action, *args)
144
+ rpc_exec_workflow_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, action, *args)
159
145
  end
160
- cast_request_to_openerp!(args[-2]) if args[-2].is_a? Hash
161
- end
162
146
 
163
- def cast_request_to_openerp!(map)
164
- map.each do |k, v|
165
- if v == nil
166
- map[k] = false
167
- elsif !v.is_a?(Integer) && !v.is_a?(Float) && v.is_a?(Numeric) && v.respond_to?(:to_f)
168
- map[k] = v.to_f
169
- elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:sec) && v.respond_to?(:year)#really ensure that's a datetime type
170
- map[k] = "#{v.year}-#{v.month}-#{v.day} #{v.hour}:#{v.min}:#{v.sec}"
171
- elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:day) && v.respond_to?(:year)#really ensure that's a date type
172
- map[k] = "#{v.year}-#{v.month}-#{v.day}"
173
- end
147
+ def rpc_exec_workflow_with_all(db, uid, pass, obj, action, *args)
148
+ clean_request_args!(args)
149
+ reload_fields_definition()
150
+ logger.debug "OOOR RPC: 'exec_workflow', db: #{db}, uid: #{uid}, pass: #, obj: #{obj}, action: #{action}, *args: #{args.inspect}"
151
+ cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("exec_workflow", db, uid, pass, obj, action, *args))
174
152
  end
175
- end
176
153
 
177
- def cast_answer_to_ruby!(answer)
178
- reload_fields_definition()
179
-
180
- def cast_map_to_ruby!(map)
181
- map.each do |k, v|
182
- if self.fields[k] && v.is_a?(String) && !v.empty?
183
- case self.fields[k]['type']
184
- when 'datetime'
185
- map[k] = Time.parse(v)
186
- when 'date'
187
- map[k] = Date.parse(v)
188
- end
189
- end
154
+ def old_wizard_step(wizard_name, ids, step='init', wizard_id=nil, form={}, context={}, report_type='pdf')
155
+ context = @ooor.global_context.merge(context)
156
+ cast_request_to_openerp!(form)
157
+ unless wizard_id
158
+ logger.debug "OOOR RPC: 'create old_wizard_step' #{wizard_name}"
159
+ wizard_id = cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("create", @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], wizard_name))
190
160
  end
161
+ params = {'model' => @openerp_model, 'form' => form, 'report_type' => report_type}
162
+ params.merge!({'id' => ids[0], 'ids' => ids}) if ids
163
+ logger.debug "OOOR RPC: 'execute old_wizard_step' #{wizard_id}, #{params.inspect}, #{step}, #{context}"
164
+ [wizard_id, cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("execute", @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], wizard_id, params, step, context))]
191
165
  end
192
166
 
193
- if answer.is_a?(Array)
194
- answer.each {|item| self.cast_map_to_ruby!(item) if item.is_a? Hash}
195
- elsif answer.is_a?(Hash)
196
- self.cast_map_to_ruby!(answer)
197
- else
198
- answer
167
+ def method_missing(method_symbol, *arguments)
168
+ raise RuntimeError.new("Invalid RPC method: #{method_symbol}") if [:type!, :allowed!].index(method_symbol)
169
+ self.rpc_execute(method_symbol.to_s, *arguments)
199
170
  end
200
- end
201
-
202
- def method_missing(method_symbol, *arguments)
203
- raise RuntimeError.new("Invalid RPC method: #{method_symbol}") if [:type!, :allowed!].index(method_symbol)
204
- self.rpc_execute(method_symbol.to_s, *arguments)
205
- end
206
171
 
207
172
 
208
- # ******************** finders low level implementation ********************
173
+ # ******************** finders low level implementation ********************
174
+ private
209
175
 
210
- private
176
+ def find_every(options)
177
+ domain = options[:domain]
178
+ context = options[:context] || {}
179
+ prefix_options, domain = split_options(options[:params]) unless domain
180
+ ids = rpc_execute('search', to_openerp_domain(domain), options[:offset] || 0, options[:limit] || false, options[:order] || false, context)
181
+ !ids.empty? && ids[0].is_a?(Integer) && find_single(ids, options) || []
182
+ end
211
183
 
212
- def find_every(options)
213
- domain = options[:domain]
214
- context = options[:context] || {}
215
- unless domain
184
+ #actually finds many resources specified with scope = ids_array
185
+ def find_single(scope, options)
186
+ fields = options[:fields] || options[:only] || []
187
+ context = options[:context] || {}
216
188
  prefix_options, query_options = split_options(options[:params])
217
- domain = []
218
- query_options.each_pair do |k, v|
219
- domain.push [k.to_s, '=', v]
189
+ is_collection = true
190
+ scope = [scope] and is_collection = false if !scope.is_a? Array
191
+ scope.map! do |item|
192
+ if item.is_a?(String) && item.to_i == 0#triggers ir_model_data absolute reference lookup
193
+ tab = item.split(".")
194
+ domain = [['name', '=', tab[-1]]]
195
+ domain += [['module', '=', tab[-2]]] if tab[-2]
196
+ ir_model_data = const_get('ir.model.data').find(:first, :domain => domain)
197
+ ir_model_data && ir_model_data.res_id && search([['id', '=', ir_model_data.res_id]])[0]
198
+ else
199
+ item
200
+ end
201
+ end.reject! {|item| !item}
202
+ records = rpc_execute('read', scope, fields, context)
203
+ records = records.sort_by {|r| scope.index(r["id"])} #TODO use sort_by! in Ruby 1.9
204
+ active_resources = []
205
+ records.each do |record|
206
+ r = {}
207
+ record.each_pair do |k,v|
208
+ r[k.to_sym] = v
209
+ end
210
+ active_resources << instantiate_record(r, prefix_options, context)
220
211
  end
212
+ unless is_collection
213
+ return active_resources[0]
214
+ end
215
+ return active_resources
221
216
  end
222
- ids = rpc_execute('search', domain, options[:offset] || 0, options[:limit] || false, options[:order] || false, context)
223
- !ids.empty? && ids[0].is_a?(Integer) && find_single(ids, options) || []
224
- end
225
217
 
226
- #actually finds many resources specified with scope = ids_array
227
- def find_single(scope, options)
228
- fields = options[:fields] || []
229
- context = options[:context] || {}
230
- prefix_options, query_options = split_options(options[:params])
231
- is_collection = true
232
- scope = [scope] and is_collection = false if !scope.is_a? Array
233
- scope.map! do |item|
234
- if item.is_a?(String) && item.to_i == 0#triggers ir_model_data absolute reference lookup
235
- tab = item.split(".")
236
- domain = [['name', '=', tab[-1]]]
237
- domain += [['module', '=', tab[-2]]] if tab[-2]
238
- ir_model_data = const_get('ir.model.data').find(:first, :domain => domain)
239
- ir_model_data && ir_model_data.res_id && search([['id', '=', ir_model_data.res_id]])[0]
240
- else
241
- item
242
- end
243
- end.reject! {|item| !item}
244
- records = rpc_execute('read', scope, fields, context)
245
- active_resources = []
246
- records.each do |record|
247
- r = {}
248
- record.each_pair do |k,v|
249
- r[k.to_sym] = v
218
+ #overriden because loading default fields is all the rage but we don't want them when reading a record
219
+ def instantiate_record(record, prefix_options = {}, context = {})
220
+ new(record, [], context).tap do |resource|
221
+ resource.prefix_options = prefix_options
250
222
  end
251
- active_resources << instantiate_record(r, prefix_options, context)
252
223
  end
253
- unless is_collection
254
- return active_resources[0]
255
- end
256
- return active_resources
257
- end
258
224
 
259
- #overriden because loading default fields is all the rage but we don't want them when reading a record
260
- def instantiate_record(record, prefix_options = {}, context = {})
261
- new(record, [], context).tap do |resource|
262
- resource.prefix_options = prefix_options
263
- end
264
225
  end
265
226
 
266
- end
267
-
227
+ self.name = "OpenObjectResource"
228
+
229
+
230
+ # ******************** instance methods ********************
268
231
 
269
- # ******************** instance methods ********************
232
+ attr_accessor :associations, :loaded_associations, :ir_model_data_id, :object_session
270
233
 
271
- attr_accessor :relations, :loaded_relations, :ir_model_data_id, :object_session
272
-
273
- def object_db; object_session[:database] || self.class.database || self.class.ooor.config[:database]; end
274
- def object_uid;object_session[:user_id] || self.class.user_id || self.class.ooor.config[:user_id]; end
275
- def object_pass; object_session[:password] || self.class.password || self.class.ooor.config[:password]; end
276
-
277
- #try to wrap the object context inside the query.
278
- def rpc_execute(method, *args)
279
- if args[-1].is_a? Hash
280
- args[-1] = self.class.ooor.global_context.merge(object_session[:context]).merge(args[-1])
281
- elsif args.is_a?(Array)
282
- args += [self.class.ooor.global_context.merge(object_session[:context])]
283
- end
284
- self.class.rpc_execute_with_all(object_db, object_uid, object_pass, self.class.openerp_model, method, *args)
285
- end
234
+ def object_db; object_session[:database] || self.class.database || self.class.ooor.config[:database]; end
235
+ def object_uid;object_session[:user_id] || self.class.user_id || self.class.ooor.config[:user_id]; end
236
+ def object_pass; object_session[:password] || self.class.password || self.class.ooor.config[:password]; end
286
237
 
287
- def cast_relations_to_openerp!
288
- @relations.reject! do |k, v| #reject non asigned many2one or empty list
289
- v.is_a?(Array) && (v.size == 0 or v[1].is_a?(String))
238
+ #try to wrap the object context inside the query.
239
+ def rpc_execute(method, *args)
240
+ if args[-1].is_a? Hash
241
+ args[-1] = self.class.ooor.global_context.merge(object_session[:context]).merge(args[-1])
242
+ elsif args.is_a?(Array)
243
+ args += [self.class.ooor.global_context.merge(object_session[:context])]
244
+ end
245
+ self.class.rpc_execute_with_all(object_db, object_uid, object_pass, self.class.openerp_model, method, *args)
290
246
  end
291
247
 
292
- def cast_relation(k, v, one2many_relations, many2many_relations)
293
- if one2many_relations[k]
294
- return v.collect! do |value|
295
- if value.is_a?(OpenObjectResource) #on the fly creation as in the GTK client
296
- [0, 0, value.to_openerp_hash!]
297
- else
298
- [1, value, {}]
248
+ def reload_from_record!(record) load(record.attributes, record.associations) end
249
+
250
+ def load(attributes, associations={})#an attribute might actually be a association too, will be determined here
251
+ self.class.reload_fields_definition()
252
+ raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
253
+ @prefix_options, attributes = split_options(attributes)
254
+ @associations = associations
255
+ @attributes = {}
256
+ @loaded_associations = {}
257
+ attributes.each do |key, value|
258
+ skey = key.to_s
259
+ if self.class.associations_keys.index(skey) || value.is_a?(Array)
260
+ associations[skey] = value #the association because we want the method to load the association through method missing
261
+ else
262
+ case value
263
+ when Hash
264
+ resource = find_or_create_resource_for(key) #TODO check!
265
+ @attributes[skey] = resource@attributes[skey].new(value)
266
+ else
267
+ @attributes[skey] = value
299
268
  end
300
269
  end
301
- elsif many2many_relations[k]
302
- return v = [[6, 0, v]]
303
270
  end
271
+ self
304
272
  end
305
273
 
306
- @relations.each do |k, v| #see OpenERP awkward relations API
307
- #already casted, possibly before server error!
308
- next if (v.is_a?(Array) && v.size == 1 && v[0].is_a?(Array)) \
309
- || self.class.many2one_relations[k] \
310
- || !v.is_a?(Array)
311
- new_rel = self.cast_relation(k, v, self.class.one2many_relations, self.class.many2many_relations)
312
- if new_rel #matches a known o2m or m2m
313
- @relations[k] = new_rel
314
- else
315
- self.class.many2one_relations.each do |k2, field| #try to cast the relation to an inherited o2m or m2m:
316
- linked_class = self.class.const_get(field['relation'])
317
- new_rel = self.cast_relation(k, v, linked_class.one2many_relations, linked_class.many2many_relations)
318
- @relations[k] = new_rel and break if new_rel
319
- end
320
- end
274
+ def load_association(model_key, ids, *arguments)
275
+ options = arguments.extract_options!
276
+ related_class = self.class.const_get(model_key)
277
+ related_class.send :find, ids, :fields => options[:fields] || options[:only] || [], :context => options[:context] || {}
321
278
  end
322
- end
323
279
 
324
- def reload_from_record!(record) load(record.attributes, record.relations) end
325
-
326
- def load(attributes, relations={})#an attribute might actually be a relation too, will be determined here
327
- self.class.reload_fields_definition()
328
- raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
329
- @prefix_options, attributes = split_options(attributes)
330
- @relations = relations
331
- @attributes = {}
332
- @loaded_relations = {}
333
- attributes.each do |key, value|
334
- skey = key.to_s
335
- if self.class.relations_keys.index(skey) || value.is_a?(Array)
336
- relations[skey] = value #the relation because we want the method to load the association through method missing
280
+ def available_fields
281
+ msg = "\n*** AVAILABLE FIELDS ON OBJECT #{self.class.name} ARE: ***"
282
+ msg << "\n\n" << self.class.fields.sort {|a,b| a[1]['type']<=>b[1]['type']}.map {|i| "#{i[1]['type']} --- #{i[0]}"}.join("\n")
283
+ msg << "\n\n" << self.class.many2one_associations.map {|k, v| "many2one --- #{v['relation']} --- #{k}"}.join("\n")
284
+ msg << "\n\n" << self.class.one2many_associations.map {|k, v| "one2many --- #{v['relation']} --- #{k}"}.join("\n")
285
+ msg << "\n\n" << self.class.many2many_associations.map {|k, v| "many2many --- #{v['relation']} --- #{k}"}.join("\n")
286
+ msg << "\n\n" << self.class.polymorphic_m2o_associations.map {|k, v| "polymorphic_m2o --- #{v['relation']} --- #{k}"}.join("\n")
287
+ end
288
+
289
+ #takes care of reading OpenERP default field values.
290
+ def initialize(attributes = {}, default_get_list=false, context={})
291
+ @attributes = {}
292
+ @prefix_options = {}
293
+ @ir_model_data_id = attributes.delete(:ir_model_data_id)
294
+ @object_session = {}
295
+ @object_session[:user_id] = context.delete :user_id
296
+ @object_session[:database] = context.delete :database
297
+ @object_session[:password] = context.delete :password
298
+ @object_session[:context] = context
299
+ if default_get_list == []
300
+ load(attributes)
337
301
  else
338
- case value
339
- when Hash
340
- resource = find_or_create_resource_for(key) #TODO check!
341
- @attributes[skey] = resource@attributes[skey].new(value)
342
- else
343
- @attributes[skey] = value
344
- end
302
+ self.class.reload_fields_definition()
303
+ attributes = rpc_execute("default_get", default_get_list || self.class.fields.keys + self.class.associations_keys, @object_session[:context]).symbolize_keys!.merge(attributes.symbolize_keys!)
304
+ load(attributes)
345
305
  end
346
306
  end
347
- self
348
- end
349
-
350
- def load_relation(model_key, ids, *arguments)
351
- options = arguments.extract_options!
352
- related_class = self.class.const_get(model_key)
353
- related_class.send :find, ids, :fields => options[:fields] || [], :context => options[:context] || {}
354
- end
355
307
 
356
- def display_available_fields
357
- msg = "\n*** AVAILABLE FIELDS ON OBJECT #{self} ARE: ***"
358
- msg << "\n\n" << self.class.fields.sort {|a,b| a[1]['type']<=>b[1]['type']}.map {|i| "#{i[1]['type']} --- #{i[0]}"}.join("\n")
359
- msg << "\n\n" << self.class.many2one_relations.map {|k, v| "many2one --- #{v['relation']} --- #{k}"}.join("\n")
360
- msg << "\n\n" << self.class.one2many_relations.map {|k, v| "one2many --- #{v['relation']} --- #{k}"}.join("\n")
361
- msg << "\n\n" << self.class.many2many_relations.map {|k, v| "many2many --- #{v['relation']} --- #{k}"}.join("\n")
362
- msg << "\n\n" << self.class.polymorphic_m2o_relations.map {|k, v| "polymorphic_m2o --- #{v['relation']} --- #{k}"}.join("\n")
363
- self.class.logger.debug msg
364
- end
365
-
366
- def to_openerp_hash!
367
- cast_relations_to_openerp!
368
- @attributes.reject {|key, value| key == 'id'}.merge(@relations)
369
- end
370
-
371
- #takes care of reading OpenERP default field values.
372
- def initialize(attributes = {}, default_get_list=false, context={})
373
- @attributes = {}
374
- @prefix_options = {}
375
- @ir_model_data_id = attributes.delete(:ir_model_data_id)
376
- @object_session = {}
377
- @object_session[:user_id] = context.delete :user_id
378
- @object_session[:database] = context.delete :database
379
- @object_session[:password] = context.delete :password
380
- @object_session[:context] = context
381
- if default_get_list == []
382
- load(attributes)
383
- else
384
- self.class.reload_fields_definition()
385
- load(rpc_execute("default_get", default_get_list || self.class.fields.keys + self.class.relations_keys, @object_session[:context]).symbolize_keys!.merge(attributes.symbolize_keys!))
308
+ def save(context={}, reload=true)
309
+ new? ? create(context, reload) : update(context, reload)
386
310
  end
387
- end
388
-
389
- def save(context={}, reload=true)
390
- new? ? create(context, reload) : update(context, reload)
391
- end
392
-
393
- #compatible with the Rails way but also supports OpenERP context
394
- def create(context={}, reload=true)
395
- self.id = rpc_execute('create', to_openerp_hash!, context)
396
- IrModelData.create(:model => self.class.openerp_model, :module => @ir_model_data_id[0], :name=> @ir_model_data_id[1], :res_id => self.id) if @ir_model_data_id
397
- reload_from_record!(self.class.find(self.id, :context => context)) if reload
398
- end
399
-
400
- #compatible with the Rails way but also supports OpenERP context
401
- def update(context={}, reload=true)
402
- rpc_execute('write', [self.id], to_openerp_hash!, context)
403
- reload_from_record!(self.class.find(self.id, :context => context)) if reload
404
- end
405
-
406
- #compatible with the Rails way but also supports OpenERP context
407
- def destroy(context={})
408
- rpc_execute('unlink', [self.id], context)
409
- end
410
311
 
411
- #OpenERP copy method, load persisted copied Object
412
- def copy(defaults={}, context={})
413
- self.class.find(rpc_execute('copy', self.id, defaults, context), :context => context)
414
- end
415
-
416
- #Generic OpenERP rpc method call
417
- def call(method, *args) rpc_execute(method, *args) end
312
+ #compatible with the Rails way but also supports OpenERP context
313
+ def create(context={}, reload=true)
314
+ self.id = rpc_execute('create', to_openerp_hash!, context)
315
+ IrModelData.create(:model => self.class.openerp_model, :module => @ir_model_data_id[0], :name=> @ir_model_data_id[1], :res_id => self.id) if @ir_model_data_id
316
+ reload_from_record!(self.class.find(self.id, :context => context)) if reload
317
+ end
418
318
 
419
- #Generic OpenERP on_change method
420
- def on_change(on_change_method, field_name, field_value, *args)
421
- result = self.class.rpc_execute_with_all(object_db, object_uid, object_pass, self.class.openerp_model, on_change_method, self.id && [id] || [], *args) #OpenERP doesn't accept context systematically in on_change events unfortunately
422
- if result["warning"]
423
- self.class.logger.info result["warning"]["title"]
424
- self.class.logger.info result["warning"]["message"]
319
+ #compatible with the Rails way but also supports OpenERP context
320
+ def update(context={}, reload=true)
321
+ rpc_execute('write', [self.id], to_openerp_hash!, context)
322
+ reload_from_record!(self.class.find(self.id, :context => context)) if reload
425
323
  end
426
- load(@attributes.merge({field_name => field_value}).merge(result["value"]), @relations)
427
- end
428
324
 
429
- #wrapper for OpenERP exec_workflow Business Process Management engine
430
- def wkf_action(action, context={}, reload=true)
431
- self.class.rpc_exec_workflow_with_all(object_db, object_uid, object_pass, self.class.openerp_model, action, self.id) #FIXME looks like OpenERP exec_workflow doesn't accept context but it might be a bug
432
- reload_from_record!(self.class.find(self.id, :context => context)) if reload
433
- end
325
+ #compatible with the Rails way but also supports OpenERP context
326
+ def destroy(context={})
327
+ rpc_execute('unlink', [self.id], context)
328
+ end
434
329
 
435
- def old_wizard_step(wizard_name, step='init', wizard_id=nil, form={}, context={})
436
- result = self.class.old_wizard_step(wizard_name, [self.id], step, wizard_id, form, {})
437
- FormModel.new(wizard_name, result[0], nil, nil, result[1], [self], self.class.ooor.global_context)
438
- end
330
+ #OpenERP copy method, load persisted copied Object
331
+ def copy(defaults={}, context={})
332
+ self.class.find(rpc_execute('copy', self.id, defaults, context), :context => context)
333
+ end
439
334
 
440
- def type() method_missing(:type) end #skips deprecated Object#type method
335
+ #Generic OpenERP rpc method call
336
+ def call(method, *args) rpc_execute(method, *args) end
441
337
 
338
+ #Generic OpenERP on_change method
339
+ def on_change(on_change_method, field_name, field_value, *args)
340
+ result = self.class.rpc_execute_with_all(object_db, object_uid, object_pass, self.class.openerp_model, on_change_method, self.id && [id] || [], *args) #OpenERP doesn't accept context systematically in on_change events unfortunately
341
+ if result["warning"]
342
+ self.class.logger.info result["warning"]["title"]
343
+ self.class.logger.info result["warning"]["message"]
344
+ end
345
+ load(@attributes.merge({field_name => field_value}).merge(result["value"]), @associations)
346
+ end
442
347
 
443
- # ******************** fake associations like much like ActiveRecord according to the cached OpenERP data model ********************
348
+ #wrapper for OpenERP exec_workflow Business Process Management engine
349
+ def wkf_action(action, context={}, reload=true)
350
+ self.class.rpc_exec_workflow_with_all(object_db, object_uid, object_pass, self.class.openerp_model, action, self.id) #FIXME looks like OpenERP exec_workflow doesn't accept context but it might be a bug
351
+ reload_from_record!(self.class.find(self.id, :context => context)) if reload
352
+ end
444
353
 
445
- def relationnal_result(method_name, *arguments)
446
- self.class.reload_fields_definition()
447
- if self.class.many2one_relations.has_key?(method_name)
448
- load_relation(self.class.many2one_relations[method_name]['relation'], @relations[method_name].is_a?(Integer) && @relations[method_name] || @relations[method_name][0], *arguments)
449
- elsif self.class.one2many_relations.has_key?(method_name)
450
- load_relation(self.class.one2many_relations[method_name]['relation'], @relations[method_name], *arguments)
451
- elsif self.class.many2many_relations.has_key?(method_name)
452
- load_relation(self.class.many2many_relations[method_name]['relation'], @relations[method_name], *arguments)
453
- elsif self.class.polymorphic_m2o_relations.has_key?(method_name)
454
- values = @relations[method_name].split(',')
455
- load_relation(values[0], values[1].to_i, *arguments)
456
- else
457
- false
354
+ def old_wizard_step(wizard_name, step='init', wizard_id=nil, form={}, context={})
355
+ result = self.class.old_wizard_step(wizard_name, [self.id], step, wizard_id, form, {})
356
+ FormModel.new(wizard_name, result[0], nil, nil, result[1], [self], self.class.ooor.global_context)
458
357
  end
459
- end
358
+
359
+ def log(message, context={}) rpc_execute('log', id, message, context) end
360
+
361
+ def type() method_missing(:type) end #skips deprecated Object#type method
460
362
 
461
- def method_missing(method_symbol, *arguments)
462
- method_name = method_symbol.to_s
463
- is_assign = method_name.end_with?('=')
464
- method_key = method_name.sub('=', '')
465
- return super if attributes.has_key?(method_key)
466
- return rpc_execute(method_name, *arguments) unless arguments.empty? || is_assign
467
-
468
- self.class.reload_fields_definition()
469
-
470
- if is_assign
471
- known_relations = self.class.relations_keys + self.class.many2one_relations.collect {|k, field| self.class.const_get(field['relation']).relations_keys}.flatten
472
- if known_relations.index(method_key)
473
- @relations[method_key] = arguments[0]
474
- @loaded_relations[method_key] = arguments[0]
475
- return
363
+ # fakes associations like much like ActiveRecord according to the cached OpenERP data model
364
+ def relationnal_result(method_name, *arguments)
365
+ self.class.reload_fields_definition()
366
+ if self.class.many2one_associations.has_key?(method_name)
367
+ load_association(self.class.many2one_associations[method_name]['relation'], @associations[method_name].is_a?(Integer) && @associations[method_name] || @associations[method_name][0], *arguments)
368
+ elsif self.class.one2many_associations.has_key?(method_name)
369
+ load_association(self.class.one2many_associations[method_name]['relation'], @associations[method_name], *arguments) || []
370
+ elsif self.class.many2many_associations.has_key?(method_name)
371
+ load_association(self.class.many2many_associations[method_name]['relation'], @associations[method_name], *arguments) || []
372
+ elsif self.class.polymorphic_m2o_associations.has_key?(method_name)
373
+ values = @associations[method_name].split(',')
374
+ load_association(values[0], values[1].to_i, *arguments)
375
+ else
376
+ false
476
377
  end
477
- know_fields = self.class.fields.keys + self.class.many2one_relations.collect {|k, field| self.class.const_get(field['relation']).fields.keys}.flatten
478
- @attributes[method_key] = arguments[0] and return if know_fields.index(method_key)
479
378
  end
379
+
380
+ def method_missing(method_symbol, *arguments)
381
+ method_name = method_symbol.to_s
382
+ is_assign = method_name.end_with?('=')
383
+ method_key = method_name.sub('=', '')
384
+ self.class.reload_fields_definition()
480
385
 
481
- return @loaded_relations[method_name] if @loaded_relations.has_key?(method_name)
482
- return false if @relations.has_key?(method_name) and (!@relations[method_name] || @relations[method_name].is_a?(Array) && !@relations[method_name][0])
386
+ if attributes.has_key?(method_key)
387
+ return super
388
+ elsif @loaded_associations.has_key?(method_name)
389
+ @loaded_associations[method_name]
390
+ elsif @associations.has_key?(method_name)
391
+ result = relationnal_result(method_name, *arguments)
392
+ @loaded_associations[method_name] = result and return result if result
393
+ elsif self.class.fields.has_key?(method_key) || self.class.associations_keys.index(method_name) #unloaded field/association
394
+ load(rpc_execute('read', [id], [method_key], *arguments)[0] || {})
395
+ return method_missing(method_key, *arguments)
396
+ elsif is_assign
397
+ known_associations = self.class.associations_keys + self.class.many2one_associations.collect {|k, field| self.class.const_get(field['relation']).associations_keys}.flatten
398
+ if known_associations.index(method_key)
399
+ @associations[method_key] = arguments[0]
400
+ @loaded_associations[method_key] = arguments[0]
401
+ return
402
+ end
403
+ know_fields = self.class.fields.keys + self.class.many2one_associations.collect {|k, field| self.class.const_get(field['relation']).fields.keys}.flatten
404
+ @attributes[method_key] = arguments[0] and return if know_fields.index(method_key)
405
+ elsif id #it's an action
406
+ arguments += [{}] unless arguments.last.is_a?(Hash)
407
+ rpc_execute(method_key, [id], *arguments) #we assume that's an action
408
+ else
409
+ super
410
+ end
483
411
 
484
- if self.class.relations_keys.index(method_name) && !@relations[method_name]
485
- return self.class.many2one_relations.index(method_name) ? nil : []
486
- end
487
- result = relationnal_result(method_name, *arguments)
488
- @loaded_relations[method_name] = result and return result if result
489
-
490
- if id
491
- arguments += [{}] unless arguments.last.is_a?(Hash)
492
- rpc_execute(method_key, [id], *arguments) #we assume that's an action
493
- else
494
- super
412
+ rescue RuntimeError => e
413
+ e.message << "\n" + available_fields if e.message.index("AttributeError")
414
+ raise e
495
415
  end
496
416
 
497
- rescue RuntimeError
498
- raise
499
- rescue NoMethodError
500
- display_available_fields
501
- raise
502
417
  end
503
-
504
- end
418
+ end