ooor 1.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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 [Raphaël Valyi]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,268 @@
1
+ OOOR - OpenObject On Rails
2
+ ====
3
+
4
+ <table>
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>
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/site/assetserversite/_/rsrc/1257126470309/home/akretion_s.png" width="228px" height="124px" /></a></td>
9
+ <td>
10
+ OOOR stands for OpenObject On Rails. OpenObject is the RAD framework behind OpenERP,
11
+ the ERP that doesn't hurt, just like Rails is "web development that doesn't hurt".
12
+ So OOOR exposes seamlessly your OpenOpbject application, to your custom Rails application.
13
+ Needless to say, OOOR doubly doesn't hurt.
14
+ Furthermore, OOOR only depends on the "activeresource" gem. So it can even be used
15
+ in any (J)Ruby application without Rails.
16
+ </td>
17
+ </tr>
18
+ </table>
19
+
20
+
21
+ Why?
22
+ ------------
23
+
24
+ OpenERP makes it really straightforward to create/customize business applications with:
25
+
26
+ * standard ERP business modules (more than 300 modules)
27
+ * complex relationnal data model, with automated migration and backoffice interfaces
28
+ * ACID transactions on PostgreSQL
29
+ * role based
30
+ * modular
31
+ * integrated BPM (Business Process Management)
32
+ * integrated reporting system, with integrated translations
33
+ * both native GTK/QT clients and standard web access
34
+
35
+ In a word OpenERP really shines when it's about quickly creating the backoffice of those enterprise applications.
36
+ OpenERP is a bit higher level than Rails (for instance it's component oriented while Rails is REST oriented) so if you adhere to the OpenERP conventions,
37
+ then you are done faster than coding a Rails app (seriously).
38
+ Adhering means: you stick to OpenObject views, widgets, look and feel, components composition, ORM (kind of ActiveRecord), the Postgres database...
39
+
40
+ But sometimes you can't afford that. Typicall examples are B2C end users applications like e-commerce shops.
41
+ So what happens if you don't adhere to the OpenERP framework?
42
+ Well that's where OOOR comes into action. It allows you to build a Rails application much like you want, where you totally control the end user presentation and interaction.
43
+ But OOOR makes it straightforward to use a standard OpenERP models as your persistent models.
44
+
45
+ An other reason why you might want to use OOOR is because you would like to code essentially a Rails or say web application
46
+ (because you know it better, because the framework is cleaner or because you will reuse something, possibly Java libraries though JRuby)
47
+ but you still want to benefit from OpenERP features.
48
+
49
+ Yet an other typicall use case would be to test your OpenERP application/module using Rails best of bread BDD Ruby frameworks such as RSpec or Cucumber.
50
+
51
+ Finally you might also want to use OOOR simply to expose your OpenERP through REST to other consumer applications. Since OOOR just does that too out of the box.
52
+
53
+
54
+
55
+ How?
56
+ ------------
57
+
58
+ OpenERP is a Python based open source ERP. Every action in OpenERP is actually invokable as a webservice (SOA orientation, close to being RESTful).
59
+ OOOR just takes advantage of brings this power your favorite web development tool - Rails - with OpenERP domain objects and business methods.
60
+
61
+ OOOR aims at being a very simple piece of code (< 500 lines of code; e.g no bug, easy to evolve) adhering to Rails standards.
62
+ So instead of re-inventing the wheel, OOOR basically just sits on the top of Rails ActiveResource::Base, the standard way of remoting you ActiveRecord Rails models with REST.
63
+
64
+ Remember, ActiveResource is actually simpler than ActiveRecord. It's aimed at remoting ANY object model, not necessarily ActiveRecord models.
65
+ So ActiveResource is only a subset of ActiveRecord, sharing the common denominator API (integration is expected to become even more powerful in Rails 3).
66
+
67
+ OOOR implements ActiveResource public API almost fully. It means that you can remotely work on any OpenERP model using the standard ActiveResource API.
68
+
69
+ But, OOOR goes actually a bit further: it does implements model associations (one2many, many2many, many2one, single table inheritance).
70
+ Indeed, when loading the OpenERP models, we load the relational meta-model using OpenERP standard datamodel introspection services.
71
+ Then we cache that relational model and use it in OpenObjectResource.method_missing to load associations as requested.
72
+
73
+ OOOR also extends ActiveResource a bit with special request parameters (like :domain or :context) that will just map smoothly to the OpenERP native API, see API.
74
+
75
+
76
+ Installation
77
+ ------------
78
+
79
+ You can use OOOR in a standalone (J)Ruby application, or in a Rails application.
80
+ For both example we assume that you already started some OpenERP server on localhost, with XML/RPC on port 8069 (default),
81
+ with a database called 'mybase', with username 'admin' and password 'admin'.
82
+
83
+ In all case, you first need to install the ooor gem:
84
+
85
+ $ gem install ooor
86
+ (the ooor gem is hosted [on gemcutter.org here](http://gemcutter.org/gems/ooor), make sure you have it in your gem source lists, a way is to do >gem tumble)
87
+
88
+
89
+ ### Standalone (J)Ruby application:
90
+
91
+ Let's test OOOR in an irb console (irb command):
92
+ $ require 'rubygems'
93
+ $ require 'ooor'
94
+ $ include Ooor
95
+ $ Ooor.reload!({:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
96
+ This should load all your OpenERP models into Ruby proxy Activeresource objects. Of course there are option to load only some models.
97
+ Let's try to retrieve the user with id 1:
98
+ $ ResUsers.find(1)
99
+
100
+
101
+ ### (J)Ruby on Rails application:
102
+
103
+ we assume you created a working Rails application, in your config/environment.rb
104
+ Inside the Rails::Initializer.run do |config| statement, paste the following gem dependency:
105
+
106
+ $ config.gem "ooor"
107
+
108
+ Now, you should also create a ooor.yml config file in your config directory
109
+ You can copy/paste [the default ooor.yml from the OOOR gem](http://github.com/rvalyi/ooor/blob/master/ooor.yml)
110
+ and then adapt it to your OpenERP server environment.
111
+ If you set the 'bootstrap' parameter to true, OpenERP models will be loaded at the Rails startup.
112
+ That the easiest option to get started while you might not want that in production.
113
+
114
+ Then just start your Rails application, your OpenERP models will be loaded as you'll see in the Rails log.
115
+ You can then use all the OOOR API upon all loaded OpenERP models in your regular Rails code (see API usage section).
116
+
117
+ Enabling REST HTTP routes to your OpenERP models:
118
+ in your config/route.rb, you can alternatively enable routes to all your OpenERP models by addding:
119
+ $ OpenObjectsController.load_all_controllers(map)
120
+
121
+ Or only enable the route to some specific model instead (here partners):
122
+ $ map.resources :res_partner
123
+
124
+
125
+
126
+ API usage
127
+ ------------
128
+
129
+ Note: Ruby proxies objects are named after OpenERP models in but removing the '.' and using CamelCase.
130
+ we remind you that OpenERP tables are also named after OpenERP models but replacing the '.' by '_'.
131
+
132
+ Basic finders:
133
+
134
+ $ ProductProduct.find(1)
135
+ $ ProductProduct.find([1,2])
136
+ $ ProductProduct.find([1])
137
+ $ ProductProduct.find(:all)
138
+ $ ProductProduct.find(:last)
139
+
140
+
141
+ OpenERP domain support:
142
+
143
+ $ ResPartner.find(:all, :domain=>[['supplier', '=', 1],['active','=',1]])
144
+
145
+
146
+ OpenERP context support:
147
+
148
+ $ ProductProduct.find(1, :context => {:my_key => 'value'}
149
+
150
+
151
+ Request params or ActiveResource equivalence of OpenERP domain (but degraded as only the = operator is supported, else use domain):
152
+
153
+ $ Partners.find(:all, :params => {:supplier => true})
154
+
155
+
156
+ Relations (many2one, one2many, many2many) support:
157
+
158
+ $ SaleOrder.find(1).order_line
159
+ $ p = ProductProduct.find(1)
160
+ $ p.product_tmpl_id #many2one relation
161
+ $ p.tax_ids = [6, 0, [1,2]] #create many2many associations,
162
+ $ p.save #assigns taxes with id 1 and 2 as sale taxes,
163
+ see [the official OpenERP documentation](http://doc.openerp.com/developer/5_18_upgrading_server/19_1_upgrading_server.html?highlight=many2many)
164
+
165
+
166
+ Inherited relations support:
167
+
168
+ $ ProductProduct.find(1).categ_id #where categ_id is inherited from the ProductTemplate
169
+
170
+
171
+ Load only specific fields support (faster than loading all fields):
172
+
173
+ $ ProductProduct.find(1, :fields=>["state", "id"])
174
+ $ ProductProduct.find(:all, :fields=>["state", "id"])
175
+ $ ProductProduct.find([1,2], :fields=>["state", "id"])
176
+ $ ProductProduct.find(:all, :fields=>["state", "id"])
177
+ even in relations:
178
+ $ SaleOrder.find(1).order_line(:fields => ["state"])
179
+
180
+
181
+ Create:
182
+
183
+ $ pc = ProductCategory.new(:name => 'Categ From Rails!')
184
+ $ #<ProductCategory:0xb702c42c @prefix_options={}, @attributes={"name"=>"Categ From Rails!"}>
185
+ $ pc.create
186
+ $ => 14
187
+
188
+
189
+ Update:
190
+
191
+ $ pc.name = "A new name"
192
+ $ pc.save
193
+
194
+
195
+ Delete:
196
+
197
+ $ pc.destroy
198
+
199
+
200
+ Call workflow: see code; TODO document
201
+
202
+
203
+ Call aribtrary method: see code; TODO document
204
+
205
+
206
+
207
+ FAQ
208
+ ------------
209
+
210
+ ### How do I know which object or OpenERP webservice I should Invoke from OOOR?
211
+
212
+ An easy way to discover what is the sebservice to do something in OpenERP, is to use your GTK client and start it with the -l debug_rpc (or alternatively -l debug_rpc_answer) option.
213
+ 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).
214
+ 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.
215
+
216
+ ### How can I load/reload my OpenERP models into my Ruby application?
217
+
218
+ You can load/reload your models at any time (even in console), using the Ooor.reload! method, for instance:
219
+ $ Ooor.reload!({:url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
220
+ or using a config YAML file instead:
221
+ $ Ooor.reload!("config/ooor.yml")
222
+
223
+ ### Do I need to load all the OpenERP models in my Ruby application?
224
+
225
+ You can load only some OpenERP models (not all), which is faster and better in term of memory/security:
226
+ $ Ooor.reload!({:models => [res.partner, product.template, product.product], :url => 'http://localhost:8069/xmlrpc', :database => 'mybase', :username => 'admin', :password => 'admin'})
227
+
228
+
229
+ ### Isn't OOOR slow?
230
+
231
+ 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.
232
+ 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.
233
+ 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.
234
+
235
+ Also notice that using JRuby I could serve some 20% faster.
236
+
237
+ ### JRuby compatibility
238
+
239
+ Yes Ooor is fully JRuby compatible. It's even somewhat 20% faster using Java6 + last JRuby.
240
+ This might be espcially interresting if you plan to mix Java libraries with OpenERP in the same web appication.
241
+
242
+ ### Can I extend the OOOR models?
243
+
244
+ 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.
245
+ But using the Ruby open classes features, you can asbolutely re-open the ProductProduct class and add features of your own.
246
+ In you app/model directory, create a product_product.rb file with inside, the redéfinition of ProductProduct, for instance:
247
+
248
+ $ class ProductProduct < OpenObjectResource
249
+ $ def foo
250
+ $ "bar"
251
+ $ end
252
+ $ end
253
+
254
+ Now a ProductProduct resource got a method foo, returning "bar".
255
+
256
+ ### Can I extend the OOOR controllers?
257
+
258
+ Yes, you can do that, see the "how to extends models" section as it's very similar. Instead, in the app/controllers directory, you'll re-open defined controllers,
259
+ for the product.product controllers, it means creating a product_product_controller.rb file with:
260
+
261
+ $ class ProductProduct < OpenObjectsController
262
+ $ def foo
263
+ $ render :text => "bar"
264
+ $ end
265
+ $ end
266
+
267
+ Now, if you register that new method in your route.rb file GET /product_product/1/foo will render "bar" on your browser screen.
268
+ You could instead just customize the existing CRUD methods so you don't need to regiter any other route in route.rb.
@@ -0,0 +1,139 @@
1
+ require 'action_controller'
2
+
3
+ class OpenObjectsController < ActionController::Base
4
+
5
+ #TODO get lang from URL in before filter
6
+ #TODO use timezone from HTTP header and put is in context
7
+
8
+ # ******************** class methods ********************
9
+ class << self
10
+
11
+ cattr_accessor :logger
12
+
13
+ def model_class
14
+ if defined?(@model_class)
15
+ @model_class
16
+ elsif superclass != Object && superclass.model_class
17
+ superclass.model_class.dup.freeze
18
+ end
19
+ end
20
+
21
+ def model_class=(_model_class)
22
+ @model_class = _model_class
23
+ end
24
+
25
+ def ids_from_param(param)
26
+ if param.split(',').size > 0
27
+ return eval param
28
+ else
29
+ return param
30
+ end
31
+ end
32
+
33
+ def define_openerp_controller(model_key, binding)
34
+ model_class_name = OpenObjectResource.class_name_from_model_key(model_key)
35
+ controller_class_name = model_class_name + "Controller"
36
+ logger.info "registering #{controller_class_name} as a Rails ActiveResource Controller wrapper for OpenObject #{model_key} model"
37
+ eval "
38
+ class #{controller_class_name} < OpenObjectsController
39
+ self.model_class = #{model_class_name}
40
+ end
41
+ ", binding
42
+ end
43
+
44
+ def load_all_controllers(map)
45
+ OpenObjectResource.all_loaded_models.each do |model|
46
+ map.resources model.gsub('.', '_').to_sym
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+
53
+ # ******************** instance methods ********************
54
+
55
+ # GET /models
56
+ # GET /models.xml
57
+ def index
58
+ @models = self.class.model_class.find(:all)
59
+
60
+ respond_to do |format|
61
+ format.html # index.html.erb
62
+ format.xml { render :xml => @models }
63
+ end
64
+ end
65
+
66
+ # GET /models/1
67
+ # GET /models/1.xml
68
+ def show
69
+ @models = self.class.model_class.find(self.class.ids_from_param(params[:id]))
70
+
71
+ respond_to do |format|
72
+ format.html # show.html.erb
73
+ format.xml { render :xml => @models }
74
+ format.json { render :json => @models }
75
+ end
76
+ end
77
+
78
+ # GET /models/new
79
+ # GET /models/new.xml
80
+ def new
81
+ @models = self.class.model_class.new
82
+
83
+ respond_to do |format|
84
+ format.html # new.html.erb
85
+ format.xml { render :xml => @models }
86
+ end
87
+ end
88
+
89
+ # GET /models/1/edit
90
+ def edit
91
+ @models = self.class.model_class.find(params[:id])
92
+ end
93
+
94
+ # POST /models
95
+ # POST /models.xml
96
+ def create
97
+ @models = self.class.model_class.new(params[:partners])
98
+
99
+ respond_to do |format|
100
+ if @models.save
101
+ flash[:notice] = 'Model was successfully created.'
102
+ format.html { redirect_to(@models) }
103
+ format.xml { render :xml => @models, :status => :created, :location => @models }
104
+ else
105
+ format.html { render :action => "new" }
106
+ format.xml { render :xml => @models.errors, :status => :unprocessable_entity }
107
+ end
108
+ end
109
+ end
110
+
111
+ # PUT /models/1
112
+ # PUT /models/1.xml
113
+ def update
114
+ @models = self.class.model_class.find(params[:id])
115
+
116
+ respond_to do |format|
117
+ if @models.update_attributes(params[:partners])
118
+ flash[:notice] = 'Partners was successfully updated.'
119
+ format.html { redirect_to(@models) }
120
+ format.xml { head :ok }
121
+ else
122
+ format.html { render :action => "edit" }
123
+ format.xml { render :xml => @models.errors, :status => :unprocessable_entity }
124
+ end
125
+ end
126
+ end
127
+
128
+ # DELETE /models/1
129
+ # DELETE /models/1.xml
130
+ def destroy
131
+ @models = self.class.model_class.find(params[:id])
132
+ @models.destroy
133
+
134
+ respond_to do |format|
135
+ format.html { redirect_to(url_for(:action => index)) }
136
+ format.xml { head :ok }
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,263 @@
1
+ require 'xmlrpc/client'
2
+ require 'activeresource'
3
+
4
+ #TODO support name_search via search + param
5
+ #see name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=None):
6
+ #TODO support offset, limit and order
7
+ #see def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
8
+
9
+ class OpenObjectResource < ActiveResource::Base
10
+
11
+ # ******************** class methods ********************
12
+ class << self
13
+
14
+ cattr_accessor :logger
15
+ attr_accessor :openerp_id, :info, :access_ids, :name, :openerp_model, :field_ids, :state, #model class attributes assotiated to the OpenERP ir.model
16
+ :field_defined, :many2one_relations, :one2many_relations, :many2many_relations,
17
+ :openerp_database, :all_loaded_models
18
+
19
+ def class_name_from_model_key(model_key)
20
+ model_key.split('.').collect {|name_part| name_part[0..0].upcase + name_part[1..-1]}.join
21
+ end
22
+
23
+ def reload_fields_definition(force = false)
24
+ if self != IrModel and self != IrModelFields and (force or not @field_defined)#TODO have a way to force reloading @field_ids too eventually
25
+ unless @field_ids
26
+ model_def = IrModel.find(:all, :domain => [['model', '=', @openerp_model]])
27
+ @field_ids = model_def.field_id
28
+ @access_ids = model_def.access_ids
29
+ end
30
+ fields = IrModelFields.find(@field_ids)
31
+ @fields = {}
32
+ @many2one_relations = {}
33
+ @one2many_relations = {}
34
+ @many2many_relations = {}
35
+ fields.each do |field|
36
+ case field.attributes['ttype']
37
+ when 'many2one'
38
+ @many2one_relations[field.attributes['name']] = field
39
+ when 'one2many'
40
+ @one2many_relations[field.attributes['name']] = field
41
+ when 'many2many'
42
+ @many2many_relations[field.attributes['name']] = field
43
+ else
44
+ @fields[field.attributes['name']] = field
45
+ end
46
+ end
47
+ logger.info "#{fields.size} fields"
48
+ end
49
+ @field_defined = true
50
+ end
51
+
52
+ def define_openerp_model(arg, url, database, user_id, pass, binding)
53
+ param = (arg.is_a? OpenObjectResource) ? arg.attributes.merge(arg.relations) : {'model' => arg}
54
+ model_key = param['model']
55
+ @all_loaded_models ||= []
56
+ all_loaded_models.push(model_key)
57
+ model_class_name = class_name_from_model_key(model_key)
58
+ logger.info "registering #{model_class_name} as a Rails ActiveResource Model wrapper for OpenObject #{model_key} model"
59
+ definition = "
60
+ class #{model_class_name} < OpenObjectResource
61
+ self.site = '#{url}'
62
+ self.user = #{user_id}
63
+ self.password = '#{pass}'
64
+ self.openerp_database = '#{database}'
65
+ self.openerp_model = '#{model_key}'
66
+ self.openerp_id = #{param['id'] || false}
67
+ self.info = '#{param['info']}'
68
+ self.name = '#{param['name']}'
69
+ self.state = '#{param['state']}'
70
+ self.field_ids = #{(param['field_id'] and '[' + param['field_id'].join(',') + ']') || false}
71
+ self.access_ids = #{(param['access_ids'] and '[' + param['access_ids'].join(',') + ']') || false}
72
+ self.many2one_relations = {}
73
+ self.one2many_relations = {}
74
+ self.many2many_relations = {}
75
+ end"
76
+ eval definition, binding
77
+ end
78
+
79
+
80
+ # ******************** remote communication ********************
81
+
82
+ def client
83
+ @client ||= XMLRPC::Client.new2(@site.to_s.gsub(/\/$/,'')) #always remove trailing / to make OpenERP happy
84
+ end
85
+
86
+ #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
87
+ def rpc_execute(method, *args)
88
+ rpc_execute_with_object(@openerp_model, method, *args)
89
+ end
90
+
91
+ def rpc_execute_with_object(object, method, *args)
92
+ rpc_execute_with_all(@openerp_database, @user, @password, object, method, *args)
93
+ end
94
+
95
+ #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
96
+ def rpc_execute_with_all(db, uid, pass, obj, method, *args)
97
+ try_with_pretty_error_log { client.call("execute", db, uid, pass, obj, method, *args) }
98
+ end
99
+
100
+ #corresponding method for OpenERP osv.exec_workflow(self, db, uid, obj, method, *args)
101
+ def rpc_exec_workflow(method, *args)
102
+ rpc_exec_workflow_with_object(@openerp_model, method, *args)
103
+ end
104
+
105
+ def rpc_exec_workflow_with_object(object, method, *args)
106
+ rpc_exec_workflow_with_all(@openerp_database, @user, @password, object, method, *args)
107
+ end
108
+
109
+ def rpc_exec_workflow_with_all(method, *args)
110
+ try_with_pretty_error_log { client.call("exec_workflow", db, uid, pass, obj, method, *args) }
111
+ end
112
+
113
+ #grab the eventual error log from OpenERP response as OpenERP doesn't enforce carefuly
114
+ #the XML/RPC spec, see https://bugs.launchpad.net/openerp/+bug/257581
115
+ def try_with_pretty_error_log
116
+ yield
117
+ rescue RuntimeError => e
118
+ logger.error "OpenERP server error!
119
+ ***********
120
+ #{eval("#{ e }".gsub("wrong fault-structure: ", ""))["faultString"]}
121
+ ***********"""
122
+ raise
123
+ end
124
+
125
+ def load_relation(model_key, ids, *arguments)
126
+ options = arguments.extract_options!
127
+ relation_model_class = eval class_name_from_model_key(model_key)
128
+ relation_model_class.send :find, ids, :fields => options[:fields] || [], :context => options[:context] || {}
129
+ end
130
+
131
+
132
+ # ******************** finders low level implementation ********************
133
+
134
+ private
135
+
136
+ def find_every(options)
137
+ domain = options[:domain]
138
+ context = options[:context] || {}
139
+ unless domain
140
+ prefix_options, query_options = split_options(options[:params])
141
+ domain = []
142
+ query_options.each_pair do |k, v|
143
+ domain.push [k.to_s, '=', v]
144
+ end
145
+ end
146
+ ids = rpc_execute('search', domain, context)
147
+ find_single(ids, options)
148
+ end
149
+
150
+ #TODO, make sense?
151
+ def find_one
152
+ raise "Not implemented yet, go one!"
153
+ end
154
+
155
+ # Find a single resource from the default URL
156
+ def find_single(scope, options)
157
+ fields = (options[:fields] and [options[:fields]]) || []
158
+ context = options[:context] || {}
159
+ prefix_options, query_options = split_options(options[:params])
160
+ is_collection = true
161
+ if !scope.is_a? Array
162
+ scope = [scope]
163
+ is_collection = false
164
+ end
165
+ records = rpc_execute('read', scope, *(fields + [context]))
166
+ active_resources = []
167
+ records.each do |record|
168
+ r = {}
169
+ record.each_pair do |k,v|
170
+ r[k.to_sym] = v
171
+ end
172
+ active_resources << instantiate_record(r, prefix_options)
173
+ end
174
+ unless is_collection
175
+ return active_resources[0]
176
+ end
177
+ return active_resources
178
+ end
179
+
180
+ end
181
+
182
+
183
+ # ******************** instance methods ********************
184
+
185
+ def pre_cast_attributes
186
+ @attributes.each {|k, v| @attributes[k] = ((v.is_a? BigDecimal) ? Float(v) : v)}
187
+ end
188
+
189
+ def load(attributes)
190
+ self.class.reload_fields_definition unless self.class.field_defined
191
+ raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
192
+ @prefix_options, attributes = split_options(attributes)
193
+ attributes.each do |key, value|
194
+ case value
195
+ when Array
196
+ relations[key.to_s] = value #the relation because we want the method to load the association through method missing
197
+ when Hash
198
+ resource = find_or_create_resource_for(key)
199
+ @attributes[key.to_s] = resource@attributes[key.to_s].new(value)
200
+ else
201
+ @attributes[key.to_s] = value.dup rescue value
202
+ end
203
+ end
204
+
205
+ self
206
+ end
207
+
208
+ #compatible with the Rails way but also supports OpenERP context
209
+ def create(context={})
210
+ self.pre_cast_attributes
211
+ self.id = self.class.rpc_execute('create', @attributes, context)
212
+ end
213
+
214
+ #compatible with the Rails way but also supports OpenERP context
215
+ def update(context={})
216
+ self.pre_cast_attributes
217
+ self.class.rpc_execute('write', self.id, @attributes.reject{|k, v| k == 'id'}, context)
218
+ end
219
+
220
+ #compatible with the Rails way but also supports OpenERP context
221
+ def destroy(context={})
222
+ self.class.rpc_execute('unlink', self.id, context)
223
+ end
224
+
225
+
226
+ # ******************** fake associations like much like ActiveRecord according to the cached OpenERP data model ********************
227
+
228
+ def relations
229
+ @relations ||= {} and @relations
230
+ end
231
+
232
+ def relationnal_result(method_id, *arguments)
233
+ self.class.reload_fields_definition unless self.class.field_defined
234
+ if self.class.many2one_relations[method_id.to_s]
235
+ self.class.load_relation(self.class.many2one_relations[method_id.to_s].relation, @relations[method_id.to_s][0], *arguments)
236
+ elsif self.class.one2many_relations[method_id.to_s]
237
+ self.class.load_relation(self.class.one2many_relations[method_id.to_s].relation, @relations[method_id.to_s], *arguments)
238
+ elsif self.class.many2many_relations[method_id.to_s]
239
+ self.class.load_relation(self.class.many2many_relations[method_id.to_s].relation, @relations[method_id.to_s], *arguments)
240
+ else
241
+ false
242
+ end
243
+ end
244
+
245
+ def method_missing(method_id, *arguments)
246
+ result = relationnal_result(method_id, *arguments)
247
+ if result
248
+ return result
249
+ elsif @relations and @relations[method_id.to_s] and !self.class.many2one_relations.empty?
250
+ #maybe the relation is inherited or could be inferred from a related field
251
+ self.class.many2one_relations.each do |k, field|
252
+ model = self.class.load_relation(field.relation, @relations[method_id.to_s][0], *arguments)
253
+ result = model.relationnal_result(method_id, *arguments)
254
+ if result
255
+ return result
256
+ end
257
+ end
258
+ super
259
+ end
260
+ super
261
+ end
262
+
263
+ end
@@ -0,0 +1,96 @@
1
+ require 'logger'
2
+
3
+ module Ooor
4
+
5
+ @ooor_logger = ((defined?(RAILS_ENV) and RAILS_ENV != "development") ? Rails.logger : Logger.new(STDOUT))
6
+
7
+ #load the custom configuration
8
+ def self.load_config(config_file=nil, env=nil)
9
+ config_file ||= defined?(RAILS_ROOT) && "#{RAILS_ROOT}/config/ooor.yml" || 'ooor.yml'
10
+ env ||= defined?(RAILS_ENV) && RAILS_ENV || 'development'
11
+ @ooor_config = YAML.load_file(config_file)[env]
12
+ rescue SystemCallError
13
+ @ooor_logger.error """failed to load OOOR yaml configuration file.
14
+ make sure your app has a #{config_file} file correctly set up
15
+ if not, just copy/paste the default ooor.yml file from the OOOR Gem
16
+ to #{RAILS_ROOT}/config/ooor.yml and customize it properly\n\n"""
17
+ raise
18
+ end
19
+
20
+ def self.loaded?
21
+ OpenObjectResource.all_loaded_models.is_a? Array and OpenObjectResource.all_loaded_models.size > 0
22
+ end
23
+
24
+ def self.reload!(config=false, env=false, keep_config=false)
25
+ @ooor_config = config.is_a?(Hash) && config or keep_config && @ooor_config or self.load_config(config, env)
26
+ @ooor_config.symbolize_keys!
27
+
28
+ begin
29
+ url = @ooor_config[:url]
30
+ database = @ooor_config[:database]
31
+ user = @ooor_config[:username]
32
+ pass = @ooor_config[:password]
33
+ rescue Exception => error
34
+ @ooor_logger.error """ooor.yml failed: #{error.inspect}
35
+ #{error.backtrace}
36
+ You probably didn't configure the ooor.yml file properly because we can't load it"""
37
+ raise
38
+ end
39
+
40
+ require 'xmlrpc/client'
41
+ begin
42
+ login_url = url.gsub(/\/$/,'') + "/common"
43
+ client = XMLRPC::Client.new2(login_url)
44
+ user_id = client.call("login", database, user, pass)
45
+
46
+
47
+ #*************** load the models
48
+
49
+ models_url = url.gsub(/\/$/,'') + "/object"
50
+ OpenObjectResource.logger = @ooor_logger
51
+ @ooor_binding = lambda {}
52
+ OpenObjectResource.define_openerp_model("ir.model", models_url, database, user_id, pass, @ooor_binding)
53
+ OpenObjectResource.define_openerp_model("ir.model.fields", models_url, database, user_id, pass, @ooor_binding)
54
+
55
+
56
+ if @ooor_config[:models] #we load only a customized subset of the OpenERP models
57
+ models = IrModel.find(:all, :domain => [['model', 'in', @ooor_config[:models]]])
58
+ else #we load all the models
59
+ models = IrModel.find(:all)
60
+ end
61
+
62
+ models.each {|openerp_model| OpenObjectResource.define_openerp_model(openerp_model, models_url, database, user_id, pass, @ooor_binding) }
63
+
64
+
65
+ # *************** load the models REST controllers
66
+ if defined?(ActionController)
67
+ OpenObjectsController.logger = @ooor_logger
68
+ models.each {|openerp_model| OpenObjectsController.define_openerp_controller(openerp_model.model, @ooor_binding) }
69
+ end
70
+
71
+
72
+ rescue SystemCallError => error
73
+ @ooor_logger.error """login to OpenERP server failed:
74
+ #{error.inspect}
75
+ #{error.backtrace}
76
+ Are your sure the server is started? Are your login parameters correct? Can this server ping the OpenERP server?
77
+ login XML/RPC url was #{login_url}
78
+ database: #{database}; user name: #{user}; password: #{pass}
79
+ OOOR plugin not loaded! Continuing..."""
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+
87
+ require 'app/models/open_object_resource'
88
+ require 'app/controllers/open_objects_controller'
89
+
90
+
91
+ if defined?(Rails)
92
+ include Ooor
93
+ if Ooor.load_config['bootstrap']
94
+ Ooor.reload!(false, false, true)
95
+ end
96
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ooor
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Raphael Valyi - www.Akretion.com
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-31 00:00:00 -02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activeresource
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: OOOR exposes business object proxies to your Ruby (Rails or not) application, that map seamlessly to your remote OpenObject/OpenERP server using webservices. It extends the standard ActiveResource API.
26
+ email: rvalyi@akretion.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - README.md
35
+ - MIT-LICENSE
36
+ - lib/ooor.rb
37
+ - lib/app/models/open_object_resource.rb
38
+ - lib/app/controllers/open_objects_controller.rb
39
+ has_rdoc: true
40
+ homepage: http://code.google.com/p/ooor/
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: OpenObject on Rails
67
+ test_files: []
68
+