ooor 1.9.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +23 -71
- data/Rakefile +5 -0
- data/bin/ooor +1 -0
- data/lib/ooor.rb +87 -129
- data/lib/ooor/associations.rb +64 -0
- data/lib/ooor/base.rb +218 -0
- data/lib/{app/models → ooor}/base64.rb +0 -0
- data/lib/ooor/connection.rb +37 -0
- data/lib/ooor/errors.rb +120 -0
- data/lib/ooor/field_methods.rb +153 -0
- data/lib/{app → ooor}/helpers/core_helpers.rb +2 -2
- data/lib/ooor/locale.rb +13 -0
- data/lib/ooor/mini_active_resource.rb +94 -0
- data/lib/ooor/model_registry.rb +19 -0
- data/lib/ooor/naming.rb +73 -0
- data/lib/ooor/rack.rb +114 -0
- data/lib/ooor/railtie.rb +41 -0
- data/lib/ooor/reflection.rb +537 -0
- data/lib/ooor/reflection_ooor.rb +92 -0
- data/lib/{app/models → ooor}/relation.rb +61 -22
- data/lib/ooor/relation/finder_methods.rb +113 -0
- data/lib/ooor/report.rb +53 -0
- data/lib/{app/models → ooor}/serialization.rb +18 -6
- data/lib/ooor/services.rb +133 -0
- data/lib/ooor/session.rb +120 -0
- data/lib/ooor/session_handler.rb +63 -0
- data/lib/ooor/transport.rb +34 -0
- data/lib/ooor/transport/json_client.rb +53 -0
- data/lib/ooor/transport/xml_rpc_client.rb +15 -0
- data/lib/ooor/type_casting.rb +193 -0
- data/lib/ooor/version.rb +8 -0
- data/spec/helpers/test_helper.rb +11 -0
- data/spec/install_nightly.sh +17 -0
- data/spec/ooor_spec.rb +197 -79
- data/spec/requirements.txt +19 -0
- metadata +58 -20
- data/lib/app/models/client_xmlrpc.rb +0 -34
- data/lib/app/models/open_object_resource.rb +0 -486
- data/lib/app/models/services.rb +0 -47
- data/lib/app/models/type_casting.rb +0 -134
- data/lib/app/models/uml.rb +0 -210
- data/lib/app/ui/action_window.rb +0 -96
- data/lib/app/ui/client_base.rb +0 -36
- data/lib/app/ui/form_model.rb +0 -88
- data/ooor.yml +0 -27
data/lib/ooor/base.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
# OOOR: OpenObject On Ruby
|
2
|
+
# Copyright (C) 2009-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/core_ext/hash/indifferent_access'
|
7
|
+
require 'active_support/core_ext/module/delegation.rb'
|
8
|
+
require 'ooor/reflection'
|
9
|
+
require 'ooor/reflection_ooor'
|
10
|
+
|
11
|
+
module Ooor
|
12
|
+
class ModelTemplate #meta data shared across sessions
|
13
|
+
TEMPLATE_PROPERTIES = [:openerp_id, :info, :access_ids, :description,
|
14
|
+
:openerp_model, :field_ids, :state, :fields,
|
15
|
+
:many2one_associations, :one2many_associations, :many2many_associations, :polymorphic_m2o_associations, :associations_keys,
|
16
|
+
:associations, :columns, :columns_hash]
|
17
|
+
attr_accessor *TEMPLATE_PROPERTIES
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
class Base < Ooor::MiniActiveResource
|
22
|
+
#PREDEFINED_INHERITS = {'product.product' => 'product_tmpl_id'}
|
23
|
+
include Naming, TypeCasting, Serialization, ReflectionOoor, Reflection, Associations, Report, FinderMethods, FieldMethods
|
24
|
+
|
25
|
+
|
26
|
+
# ********************** class methods ************************************
|
27
|
+
class << self
|
28
|
+
|
29
|
+
attr_accessor :name, :connection, :t, :scope_prefix #template
|
30
|
+
delegate *ModelTemplate::TEMPLATE_PROPERTIES, to: :t
|
31
|
+
|
32
|
+
# ******************** remote communication *****************************
|
33
|
+
|
34
|
+
def create(attributes = {}, context={}, default_get_list=false, reload=true)
|
35
|
+
self.new(attributes, default_get_list, context).tap { |resource| resource.save(context, reload) }
|
36
|
+
end
|
37
|
+
|
38
|
+
#OpenERP search method
|
39
|
+
def search(domain=[], offset=0, limit=false, order=false, context={}, count=false)
|
40
|
+
rpc_execute(:search, to_openerp_domain(domain), offset, limit, order, context, count)
|
41
|
+
end
|
42
|
+
|
43
|
+
def name_search(name='', domain=[], operator='ilike', context={}, limit=100)
|
44
|
+
rpc_execute(:name_search, name, to_openerp_domain(domain), operator, context, limit)
|
45
|
+
end
|
46
|
+
|
47
|
+
def rpc_execute(method, *args)
|
48
|
+
object_service(:execute, openerp_model, method, *args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def rpc_exec_workflow(action, *args)
|
52
|
+
object_service(:exec_workflow, openerp_model, action, *args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def object_service(service, obj, method, *args)
|
56
|
+
reload_fields_definition(false, connection.connection_session)
|
57
|
+
cast_answer_to_ruby!(connection.object.object_service(service, obj, method, *cast_request_to_openerp(args)))
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_missing(method_symbol, *args)
|
61
|
+
raise RuntimeError.new("Invalid RPC method: #{method_symbol}") if [:type!, :allowed!].index(method_symbol)
|
62
|
+
self.rpc_execute(method_symbol.to_s, *args)
|
63
|
+
end
|
64
|
+
|
65
|
+
# ******************** AREL Minimal implementation ***********************
|
66
|
+
|
67
|
+
def relation(context={}); @relation ||= Relation.new(self, context); end #TODO template
|
68
|
+
def scoped(context={}); relation(context); end
|
69
|
+
def where(opts, *rest); relation.where(opts, *rest); end
|
70
|
+
def all(*args); relation.all(*args); end
|
71
|
+
def limit(value); relation.limit(value); end
|
72
|
+
def order(value); relation.order(value); end
|
73
|
+
def offset(value); relation.offset(value); end
|
74
|
+
|
75
|
+
def logger; Ooor.logger; end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
self.name = "Base"
|
80
|
+
|
81
|
+
# ********************** instance methods **********************************
|
82
|
+
|
83
|
+
attr_accessor :associations, :loaded_associations, :ir_model_data_id, :object_session
|
84
|
+
|
85
|
+
def rpc_execute(method, *args)
|
86
|
+
args += [self.class.connection.connection_session.merge(object_session)] unless args[-1].is_a? Hash
|
87
|
+
self.class.object_service(:execute, self.class.openerp_model, method, *args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def load(attributes, remove_root=false, persisted=false)#an attribute might actually be a association too, will be determined here
|
91
|
+
self.class.reload_fields_definition(false, object_session)
|
92
|
+
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
93
|
+
@prefix_options, attributes = split_options(attributes)
|
94
|
+
@associations ||= {}
|
95
|
+
@attributes ||= {}
|
96
|
+
@loaded_associations = {}
|
97
|
+
attributes.each do |key, value|
|
98
|
+
skey = key.to_s
|
99
|
+
if self.class.associations_keys.index(skey) || value.is_a?(Array) #FIXME may miss m2o with inherits!
|
100
|
+
@associations[skey] = value #the association because we want the method to load the association through method missing
|
101
|
+
else
|
102
|
+
@attributes[skey] = value || nil #don't bloat with false values
|
103
|
+
end
|
104
|
+
end
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
#takes care of reading OpenERP default field values.
|
109
|
+
def initialize(attributes = {}, default_get_list=false, context={}, persisted=false)
|
110
|
+
@attributes = {}
|
111
|
+
@prefix_options = {}
|
112
|
+
@ir_model_data_id = attributes.delete(:ir_model_data_id)
|
113
|
+
@object_session = {}
|
114
|
+
@object_session = HashWithIndifferentAccess.new(context)
|
115
|
+
@persisted = persisted
|
116
|
+
self.class.reload_fields_definition(false, @object_session)
|
117
|
+
if default_get_list == []
|
118
|
+
load(attributes)
|
119
|
+
else
|
120
|
+
load_with_defaults(attributes, default_get_list)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def save(context={}, reload=true)
|
125
|
+
new? ? create(context, reload) : update(context, reload)
|
126
|
+
rescue ValidationError => e
|
127
|
+
e.extract_validation_error!(errors)
|
128
|
+
return false
|
129
|
+
end
|
130
|
+
|
131
|
+
#compatible with the Rails way but also supports OpenERP context
|
132
|
+
def create(context={}, reload=true)
|
133
|
+
self.id = rpc_execute('create', to_openerp_hash, context)
|
134
|
+
if @ir_model_data_id
|
135
|
+
IrModelData.create(model: self.class.openerp_model,
|
136
|
+
'module' => @ir_model_data_id[0],
|
137
|
+
'name' => @ir_model_data_id[1],
|
138
|
+
'res_id' => self.id)
|
139
|
+
end
|
140
|
+
reload_from_record!(self.class.find(self.id, context: context)) if reload
|
141
|
+
@persisted = true
|
142
|
+
end
|
143
|
+
|
144
|
+
def update_attributes(attributes, context={}, reload=true)
|
145
|
+
load(attributes, false) && save(context, reload)
|
146
|
+
end
|
147
|
+
|
148
|
+
#compatible with the Rails way but also supports OpenERP context
|
149
|
+
def update(context={}, reload=true) #TODO use http://apidock.com/rails/ActiveRecord/Dirty to minimize data to save back
|
150
|
+
rpc_execute('write', [self.id], to_openerp_hash, context)
|
151
|
+
reload_fields(context) if reload
|
152
|
+
@persisted = true
|
153
|
+
end
|
154
|
+
|
155
|
+
#compatible with the Rails way but also supports OpenERP context
|
156
|
+
def destroy(context={})
|
157
|
+
rpc_execute('unlink', [self.id], context)
|
158
|
+
end
|
159
|
+
|
160
|
+
#OpenERP copy method, load persisted copied Object
|
161
|
+
def copy(defaults={}, context={})
|
162
|
+
self.class.find(rpc_execute('copy', self.id, defaults, context), context: context)
|
163
|
+
end
|
164
|
+
|
165
|
+
#Generic OpenERP rpc method call
|
166
|
+
def call(method, *args) rpc_execute(method, *args) end
|
167
|
+
|
168
|
+
#Generic OpenERP on_change method
|
169
|
+
def on_change(on_change_method, field_name, field_value, *args)
|
170
|
+
# NOTE: OpenERP doesn't accept context systematically in on_change events unfortunately
|
171
|
+
ids = self.id ? [id] : []
|
172
|
+
result = self.class.object_service(:execute, self.class.openerp_model, on_change_method, ids, *args)
|
173
|
+
load_on_change_result(result, field_name, field_value)
|
174
|
+
end
|
175
|
+
|
176
|
+
#wrapper for OpenERP exec_workflow Business Process Management engine
|
177
|
+
def wkf_action(action, context={}, reload=true)
|
178
|
+
self.class.object_service(:exec_workflow, self.class.openerp_model, action, self.id, object_session)
|
179
|
+
reload_fields(context) if reload
|
180
|
+
end
|
181
|
+
|
182
|
+
#Add get_report_data to obtain [report["result"],report["format]] of a concrete openERP Object
|
183
|
+
def get_report_data(report_name, report_type="pdf", context={})
|
184
|
+
self.class.get_report_data(report_name, [self.id], report_type, context)
|
185
|
+
end
|
186
|
+
|
187
|
+
def type() method_missing(:type) end #skips deprecated Object#type method
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def load_with_defaults(attributes, default_get_list)
|
192
|
+
defaults = rpc_execute("default_get", default_get_list || self.class.fields.keys + self.class.associations_keys, object_session.dup)
|
193
|
+
attributes = HashWithIndifferentAccess.new(defaults.merge(attributes.reject {|k, v| v.blank? }))
|
194
|
+
load(attributes)
|
195
|
+
end
|
196
|
+
|
197
|
+
def load_on_change_result(result, field_name, field_value)
|
198
|
+
if result["warning"]
|
199
|
+
self.class.logger.info result["warning"]["title"]
|
200
|
+
self.class.logger.info result["warning"]["message"]
|
201
|
+
end
|
202
|
+
attrs = @attributes.merge(field_name => field_value)
|
203
|
+
attrs.merge!(result["value"])
|
204
|
+
load(attrs)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Ruby 1.9.compat, See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
|
208
|
+
def to_ary; nil; end # :nodoc:
|
209
|
+
|
210
|
+
def reload_from_record!(record) load(record.attributes.merge(record.associations)) end
|
211
|
+
|
212
|
+
def reload_fields(context)
|
213
|
+
records = self.class.find(self.id, context: context, fields: @attributes.keys + @associations.keys)
|
214
|
+
reload_from_record!(records)
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
File without changes
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# OOOR: OpenObject On Ruby
|
2
|
+
# Copyright (C) 2009-2012 Akretion LTDA (<http://www.akretion.com>).
|
3
|
+
# Author: Raphaël Valyi
|
4
|
+
# Licensed under the MIT license, see MIT-LICENSE file
|
5
|
+
|
6
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
7
|
+
|
8
|
+
module Ooor
|
9
|
+
class Connection
|
10
|
+
attr_accessor :config, :connection_session
|
11
|
+
|
12
|
+
def initialize(config, env=false)
|
13
|
+
@config = _config(config)
|
14
|
+
Object.const_set(@config[:scope_prefix], Module.new) if @config[:scope_prefix]
|
15
|
+
end
|
16
|
+
|
17
|
+
def connection_session
|
18
|
+
@connection_session ||= {}.merge!(@config[:connection_session] || {})
|
19
|
+
end
|
20
|
+
|
21
|
+
def helper_paths
|
22
|
+
[File.dirname(__FILE__) + '/helpers/*', *@config[:helper_paths]]
|
23
|
+
end
|
24
|
+
|
25
|
+
def class_name_from_model_key(model_key)
|
26
|
+
model_key.split('.').collect {|name_part| name_part.capitalize}.join
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def _config(config)
|
32
|
+
c = config.is_a?(String) ? Ooor.load_config(config, env) : config
|
33
|
+
HashWithIndifferentAccess.new(c)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/ooor/errors.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module Ooor
|
2
|
+
class OpenERPServerError < RuntimeError
|
3
|
+
attr_accessor :request, :faultCode, :faultString
|
4
|
+
|
5
|
+
def self.create_from_trace(error, method, *args)
|
6
|
+
begin
|
7
|
+
#extracts the eventual error log from OpenERP response as OpenERP doesn't enforce carefully*
|
8
|
+
#the XML/RPC spec, see https://bugs.launchpad.net/openerp/+bug/257581
|
9
|
+
openerp_error_hash = eval("#{error}".gsub("wrong fault-structure: ", ""))
|
10
|
+
rescue SyntaxError
|
11
|
+
end
|
12
|
+
if openerp_error_hash.is_a? Hash
|
13
|
+
build(openerp_error_hash['faultCode'], openerp_error_hash['faultString'], method, *args)
|
14
|
+
else
|
15
|
+
return UnknownOpenERPServerError.new("method: #{method} - args: #{args.inspect}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build(faultCode, faultString, method, *args)
|
20
|
+
if faultCode =~ /AttributeError: /
|
21
|
+
return UnknownAttributeOrAssociationError.new("method: #{method} - args: #{args.inspect}", faultCode, faultString)
|
22
|
+
elsif faultCode =~ /TypeError: /
|
23
|
+
return TypeError.new(method, faultCode, faultString, *args)
|
24
|
+
elsif faultCode =~ /ValueError: /
|
25
|
+
return ValueError.new(method, faultCode, faultString, *args)
|
26
|
+
elsif faultCode =~ /ValidateError/
|
27
|
+
return ValidationError.new(method, faultCode, faultString, *args)
|
28
|
+
elsif faultCode =~ /AccessDenied/
|
29
|
+
return UnAuthorizedError.new(method, faultCode, faultString, *args)
|
30
|
+
elsif faultCode =~ /AuthenticationError: Credentials not provided/
|
31
|
+
return InvalidSessionError.new(method, faultCode, faultString, *args)
|
32
|
+
elsif faultCode =~ /SessionExpiredException/
|
33
|
+
return SessionExpiredError.new(method, faultCode, faultString, *args)
|
34
|
+
else
|
35
|
+
return new(method, faultCode, faultString, *args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(method=nil, faultCode=nil, faultString=nil, *args)
|
40
|
+
filtered_args = filter_password(args.dup())
|
41
|
+
@request = "method: #{method} - args: #{filtered_args.inspect}"
|
42
|
+
@faultCode = faultCode
|
43
|
+
@faultString = faultString
|
44
|
+
super()
|
45
|
+
end
|
46
|
+
|
47
|
+
def filter_password(args)
|
48
|
+
if args[0].is_a?(String) && (args[1].is_a?(Integer) || args[1].to_i != 0) && args[2].is_a?(String)
|
49
|
+
args[2] = "####"
|
50
|
+
end
|
51
|
+
args.map! do |arg|
|
52
|
+
if arg.is_a?(Hash)# && (arg.keys.index('password') || arg.keys.index(:password))
|
53
|
+
r = {}
|
54
|
+
arg.each do |k, v|
|
55
|
+
if k.to_s.index('password')
|
56
|
+
r[k] = "####"
|
57
|
+
else
|
58
|
+
r[k] = v
|
59
|
+
end
|
60
|
+
end
|
61
|
+
r
|
62
|
+
else
|
63
|
+
arg
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s()
|
69
|
+
s = super
|
70
|
+
line = "********************************************"
|
71
|
+
s = "\n\n#{line}\n*********** OOOR Request ***********\n#{@request}\n#{line}\n\n"
|
72
|
+
s << "\n#{line}\n*********** OpenERP Server ERROR ***********\n#{line}\n#{@faultCode}\n#{@faultString}\n#{line}\n."
|
73
|
+
s
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
class UnknownOpenERPServerError < OpenERPServerError; end
|
80
|
+
class UnAuthorizedError < OpenERPServerError; end
|
81
|
+
class TypeError < OpenERPServerError; end
|
82
|
+
class ValueError < OpenERPServerError; end
|
83
|
+
class InvalidSessionError < OpenERPServerError; end
|
84
|
+
class SessionExpiredError < OpenERPServerError; end
|
85
|
+
|
86
|
+
class ValidationError < OpenERPServerError
|
87
|
+
def extract_validation_error!(errors)
|
88
|
+
@faultCode.split("\n").each do |line|
|
89
|
+
extract_error_line!(errors, line) if line.index(': ')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def extract_error_line!(errors, line)
|
94
|
+
fields = line.split(": ")[0].split(' ').last.split(',')
|
95
|
+
msg = line.split(": ")[1]
|
96
|
+
fields.each { |field| errors.add(field.strip.to_sym, msg) }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class UnknownAttributeOrAssociationError < OpenERPServerError
|
101
|
+
attr_accessor :klass
|
102
|
+
|
103
|
+
def to_s()
|
104
|
+
s = super
|
105
|
+
s << available_fields(@klass) if @klass
|
106
|
+
s
|
107
|
+
end
|
108
|
+
|
109
|
+
def available_fields(clazz)
|
110
|
+
msg = "\n\n*** AVAILABLE FIELDS ON #{clazz.name} ARE: ***"
|
111
|
+
msg << "\n\n" << clazz.t.fields.sort {|a,b| a[1]['type']<=>b[1]['type']}.map {|i| "#{i[1]['type']} --- #{i[0]}"}.join("\n")
|
112
|
+
%w[many2one one2many many2many polymorphic_m2o].each do |kind|
|
113
|
+
msg << "\n\n"
|
114
|
+
msg << (clazz.send "#{kind}_associations").map {|k, v| "#{kind} --- #{v['relation']} --- #{k}"}.join("\n")
|
115
|
+
end
|
116
|
+
msg
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Ooor
|
4
|
+
module FieldMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def reload_fields_definition(force=false, context=connection.web_session)
|
10
|
+
if force || !fields
|
11
|
+
@t.fields = {}
|
12
|
+
@columns_hash = {}
|
13
|
+
fields_get = rpc_execute("fields_get", false, context)
|
14
|
+
fields_get.each { |k, field| reload_field_definition(k, field) }
|
15
|
+
@t.associations_keys = many2one_associations.keys + one2many_associations.keys + many2many_associations.keys + polymorphic_m2o_associations.keys
|
16
|
+
(fields.keys + associations_keys).each do |meth| #generates method handlers for auto-completion tools
|
17
|
+
define_field_method(meth)
|
18
|
+
end
|
19
|
+
one2many_associations.keys.each do |meth|
|
20
|
+
define_nested_attributes_method(meth)
|
21
|
+
end
|
22
|
+
logger.debug "#{fields.size} fields loaded in model #{self.name}"
|
23
|
+
Ooor.model_registry.set_template(connection.config, @t)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def all_fields
|
28
|
+
fields.merge(polymorphic_m2o_associations).merge(many2many_associations).merge(one2many_associations).merge(many2one_associations)
|
29
|
+
end
|
30
|
+
|
31
|
+
def fast_fields(options)
|
32
|
+
fields = all_fields
|
33
|
+
fields.keys.select do |k|
|
34
|
+
fields[k]["type"] != "binary" && (options[:include_functions] || !fields[k]["function"])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# this is used by fields_for in ActionView FormHelper
|
39
|
+
def define_nested_attributes_method(meth)
|
40
|
+
p "define_nested_attributes_method", meth
|
41
|
+
unless self.respond_to?(meth)
|
42
|
+
self.instance_eval do
|
43
|
+
define_method "#{meth}_attributes=" do |*args|
|
44
|
+
self.send :method_missing, *[meth, *args]
|
45
|
+
end
|
46
|
+
define_method "#{meth}_attributes" do |*args|
|
47
|
+
self.send :method_missing, *[meth, *args]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def define_field_method(meth)
|
56
|
+
unless self.respond_to?(meth)
|
57
|
+
self.instance_eval do
|
58
|
+
define_method meth do |*args|
|
59
|
+
self.send :method_missing, *[meth, *args]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def reload_field_definition(k, field)
|
66
|
+
case field['type']
|
67
|
+
when 'many2one'
|
68
|
+
many2one_associations[k] = field
|
69
|
+
when 'one2many'
|
70
|
+
one2many_associations[k] = field
|
71
|
+
when 'many2many'
|
72
|
+
many2many_associations[k] = field
|
73
|
+
when 'reference'
|
74
|
+
polymorphic_m2o_associations[k] = field
|
75
|
+
else
|
76
|
+
fields[k] = field if field['name'] != 'id'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def method_missing(method_symbol, *arguments)
|
82
|
+
method_name = method_symbol.to_s
|
83
|
+
method_key = method_name.sub('=', '')
|
84
|
+
self.class.reload_fields_definition(false, object_session)
|
85
|
+
if attributes.has_key?(method_key)
|
86
|
+
if method_name.end_with?('=')
|
87
|
+
attributes[method_key] = arguments[0]
|
88
|
+
else
|
89
|
+
attributes[method_key]
|
90
|
+
end
|
91
|
+
elsif @loaded_associations.has_key?(method_name)
|
92
|
+
@loaded_associations[method_name]
|
93
|
+
elsif @associations.has_key?(method_name)
|
94
|
+
result = relationnal_result(method_name, *arguments)
|
95
|
+
@loaded_associations[method_name] = result and return result if result
|
96
|
+
elsif method_name.end_with?('=')
|
97
|
+
return method_missing_value_assign(method_key, arguments)
|
98
|
+
elsif self.class.fields.has_key?(method_name) || self.class.associations_keys.index(method_name) #unloaded field/association
|
99
|
+
return lazzy_load_field(method_name, *arguments)
|
100
|
+
# check if that is not a Rails style association with an _id[s][=] suffix:
|
101
|
+
elsif method_name.match(/_id$/) && self.class.associations_keys.index(rel=method_name.gsub(/_id$/, ""))
|
102
|
+
return many2one_id_method(rel, *arguments)
|
103
|
+
elsif method_name.match(/_ids$/) && self.class.associations_keys.index(rel=method_name.gsub(/_ids$/, ""))
|
104
|
+
return x_to_many_ids_method(rel, *arguments)
|
105
|
+
elsif id
|
106
|
+
rpc_execute(method_key, [id], *arguments) #we assume that's an action
|
107
|
+
else
|
108
|
+
super
|
109
|
+
end
|
110
|
+
|
111
|
+
rescue UnknownAttributeOrAssociationError => e
|
112
|
+
e.klass = self.class
|
113
|
+
raise e
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def method_missing_value_assign(method_key, arguments)
|
119
|
+
if is_association_assignment(method_key)
|
120
|
+
@associations[method_key] = arguments[0]
|
121
|
+
@loaded_associations[method_key] = arguments[0]
|
122
|
+
elsif is_attribute_assignment(method_key)
|
123
|
+
@attributes[method_key] = arguments[0]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def is_association_assignment(method_key)
|
128
|
+
(self.class.associations_keys + self.class.many2one_associations.collect do |k, field|
|
129
|
+
klass = self.class.const_get(field['relation'])
|
130
|
+
klass.reload_fields_definition(false, object_session)
|
131
|
+
klass.t.associations_keys
|
132
|
+
end.flatten).index(method_key)
|
133
|
+
end
|
134
|
+
|
135
|
+
def is_attribute_assignment(method_key)
|
136
|
+
(self.class.fields.keys + self.class.many2one_associations.collect do |k, field|
|
137
|
+
klass = self.class.const_get(field['relation'])
|
138
|
+
klass.reload_fields_definition(false, object_session)
|
139
|
+
klass.t.fields.keys
|
140
|
+
end.flatten).index(method_key)
|
141
|
+
end
|
142
|
+
|
143
|
+
def lazzy_load_field(field_name, *arguments)
|
144
|
+
if attributes["id"]
|
145
|
+
load(rpc_execute('read', [id], [field_name], *arguments || object_session)[0] || {})
|
146
|
+
method_missing(field_name, *arguments)
|
147
|
+
else
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|