ext_ooor 2.3.0.1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +354 -0
- data/Rakefile +5 -0
- data/bin/ooor +43 -0
- data/lib/ext_ooor.rb +5 -0
- data/lib/ext_ooor/version.rb +5 -0
- data/lib/generators/ooor/install_generator.rb +18 -0
- data/lib/generators/ooor/ooor.yml +49 -0
- data/lib/ooor.rb +230 -0
- data/lib/ooor/associations.rb +78 -0
- data/lib/ooor/autosave_association.rb +197 -0
- data/lib/ooor/base.rb +130 -0
- data/lib/ooor/base64.rb +20 -0
- data/lib/ooor/callbacks.rb +18 -0
- data/lib/ooor/errors.rb +120 -0
- data/lib/ooor/field_methods.rb +213 -0
- data/lib/ooor/helpers/core_helpers.rb +83 -0
- data/lib/ooor/locale.rb +11 -0
- data/lib/ooor/mini_active_resource.rb +86 -0
- data/lib/ooor/model_registry.rb +24 -0
- data/lib/ooor/model_schema.rb +25 -0
- data/lib/ooor/naming.rb +92 -0
- data/lib/ooor/nested_attributes.rb +57 -0
- data/lib/ooor/persistence.rb +353 -0
- data/lib/ooor/rack.rb +137 -0
- data/lib/ooor/railtie.rb +27 -0
- data/lib/ooor/reflection.rb +151 -0
- data/lib/ooor/reflection_ooor.rb +121 -0
- data/lib/ooor/relation.rb +204 -0
- data/lib/ooor/relation/finder_methods.rb +153 -0
- data/lib/ooor/report.rb +53 -0
- data/lib/ooor/serialization.rb +49 -0
- data/lib/ooor/services.rb +134 -0
- data/lib/ooor/session.rb +250 -0
- data/lib/ooor/session_handler.rb +66 -0
- data/lib/ooor/transport.rb +34 -0
- data/lib/ooor/transport/json_client.rb +65 -0
- data/lib/ooor/transport/xml_rpc_client.rb +15 -0
- data/lib/ooor/type_casting.rb +223 -0
- data/lib/ooor/version.rb +8 -0
- data/spec/cli_spec.rb +129 -0
- data/spec/helpers/test_helper.rb +11 -0
- data/spec/ooor_spec.rb +867 -0
- metadata +118 -0
data/lib/ooor/base.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# OOOR: OpenObject On Ruby
|
2
|
+
# Copyright (C) 2009-2014 Akretion LTDA (<http://www.akretion.com>).
|
3
|
+
# Author: Raphaël Valyi
|
4
|
+
# Licensed under the MIT license, see MIT-LICENSE file
|
5
|
+
|
6
|
+
require 'active_support/core_ext/module/delegation.rb'
|
7
|
+
require 'active_model/attribute_methods'
|
8
|
+
require 'active_model/dirty'
|
9
|
+
require 'ooor/reflection'
|
10
|
+
require 'ooor/reflection_ooor'
|
11
|
+
require 'ooor/errors'
|
12
|
+
|
13
|
+
module Ooor
|
14
|
+
|
15
|
+
# the base class for proxies to OpenERP objects
|
16
|
+
class Base < Ooor::MiniActiveResource
|
17
|
+
include Naming, TypeCasting, Serialization, ReflectionOoor, Reflection
|
18
|
+
include Associations, Report, FinderMethods, FieldMethods, AutosaveAssociation, NestedAttributes
|
19
|
+
|
20
|
+
# ********************** class methods ************************************
|
21
|
+
class << self
|
22
|
+
|
23
|
+
attr_accessor :name, :session, :t, :scope_prefix
|
24
|
+
delegate *ModelSchema::TEMPLATE_PROPERTIES, to: :t
|
25
|
+
|
26
|
+
# ******************** remote communication *****************************
|
27
|
+
|
28
|
+
#OpenERP search method
|
29
|
+
def search(domain=[], offset=0, limit=false, order=false, context={}, count=false)
|
30
|
+
rpc_execute(:search, to_openerp_domain(domain), offset, limit, order, context, count)
|
31
|
+
end
|
32
|
+
|
33
|
+
def name_search(name='', domain=[], operator='ilike', limit=100, context={})
|
34
|
+
if session.odoo_serie < 10
|
35
|
+
rpc_execute(:name_search, name, to_openerp_domain(domain), operator, context, limit)
|
36
|
+
else
|
37
|
+
rpc_execute(:name_search, name, to_openerp_domain(domain), operator, limit)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def rpc_execute(method, *args)
|
42
|
+
object_service(:execute, openerp_model, method, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def rpc_exec_workflow(action, *args)
|
46
|
+
object_service(:exec_workflow, openerp_model, action, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def object_service(service, obj, method, *args)
|
50
|
+
reload_fields_definition(false)
|
51
|
+
cast_answer_to_ruby!(session.object.object_service(service, obj, method, *cast_request_to_openerp(args)))
|
52
|
+
end
|
53
|
+
|
54
|
+
def context
|
55
|
+
session.session_context
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(method_symbol, *args)
|
59
|
+
raise RuntimeError.new("Invalid RPC method: #{method_symbol}") if [:type!, :allowed!].index(method_symbol)
|
60
|
+
self.rpc_execute(method_symbol.to_s, *args)
|
61
|
+
end
|
62
|
+
|
63
|
+
# ******************** AREL Minimal implementation ***********************
|
64
|
+
|
65
|
+
def where(opts, *rest); relation.where(opts, *rest); end
|
66
|
+
def all(*args); relation.all(*args); end
|
67
|
+
def limit(value); relation.limit(value); end
|
68
|
+
def order(value); relation.order(value); end
|
69
|
+
def offset(value); relation.offset(value); end
|
70
|
+
def first(*args); relation.first(*args); end
|
71
|
+
def last(*args); relation.last(*args); end
|
72
|
+
def includes(*args); relation.includes(*args); end
|
73
|
+
|
74
|
+
|
75
|
+
def logger; Ooor.logger; end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def relation; @relation ||= Relation.new(self); end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
self.name = "Base"
|
84
|
+
|
85
|
+
# ********************** instance methods **********************************
|
86
|
+
|
87
|
+
attr_accessor :associations, :loaded_associations, :ir_model_data_id
|
88
|
+
|
89
|
+
include Persistence, Callbacks, ActiveModel::Dirty
|
90
|
+
|
91
|
+
def rpc_execute(method, *args)
|
92
|
+
args += [self.class.context] unless args[-1].is_a? Hash
|
93
|
+
self.class.object_service(:execute, self.class.openerp_model, method, *args)
|
94
|
+
end
|
95
|
+
|
96
|
+
#Generic OpenERP rpc method call
|
97
|
+
def call(method, *args) rpc_execute(method, *args) end
|
98
|
+
|
99
|
+
#Generic OpenERP on_change method
|
100
|
+
def on_change(on_change_method, field_name, field_value, *args)
|
101
|
+
# NOTE: OpenERP doesn't accept context systematically in on_change events unfortunately
|
102
|
+
ids = self.id ? [id] : []
|
103
|
+
result = self.class.object_service(:execute, self.class.openerp_model, on_change_method, ids, *args)
|
104
|
+
load_on_change_result(result, field_name, field_value)
|
105
|
+
end
|
106
|
+
|
107
|
+
#wrapper for OpenERP exec_workflow Business Process Management engine
|
108
|
+
def wkf_action(action, context={}, reload=true)
|
109
|
+
self.class.object_service(:exec_workflow, self.class.openerp_model, action, self.id, context)
|
110
|
+
reload_fields if reload
|
111
|
+
end
|
112
|
+
|
113
|
+
#Add get_report_data to obtain [report["result"],report["format]] of a concrete openERP Object
|
114
|
+
def get_report_data(report_name, report_type="pdf", context={})
|
115
|
+
self.class.get_report_data(report_name, [self.id], report_type, context)
|
116
|
+
end
|
117
|
+
|
118
|
+
def type() method_missing(:type) end #skips deprecated Object#type method
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def context
|
123
|
+
self.class.context
|
124
|
+
end
|
125
|
+
|
126
|
+
# Ruby 1.9.compat, See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
|
127
|
+
def to_ary; nil; end # :nodoc:
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
data/lib/ooor/base64.rb
ADDED
@@ -0,0 +1,20 @@
|
|
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
|
+
autoload :Base64, 'base64'
|
7
|
+
|
8
|
+
module Base64
|
9
|
+
def serialize_binary_from_file(binary_path)
|
10
|
+
return Base64.encode64(File.read(binary_path))
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize_binary_from_content(content)
|
14
|
+
return Base64.encode64(content)
|
15
|
+
end
|
16
|
+
|
17
|
+
def unserialize_binary(content)
|
18
|
+
return Base64.decode64(content)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Ooor
|
2
|
+
module Callbacks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
CALLBACKS = [
|
6
|
+
:before_validation, :after_validation, :before_save, :around_save, :after_save,
|
7
|
+
:before_create, :around_create, :after_create, :before_update, :around_update,
|
8
|
+
:after_update, :before_destroy, :around_destroy, :after_destroy
|
9
|
+
]
|
10
|
+
|
11
|
+
included do
|
12
|
+
extend ActiveModel::Callbacks
|
13
|
+
include ActiveModel::Validations::Callbacks
|
14
|
+
|
15
|
+
define_model_callbacks :save, :create, :update, :destroy
|
16
|
+
end
|
17
|
+
end
|
18
|
+
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: / || faultCode =~ /object has no attribute/
|
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/ || faultCode =~ /Access Denied/ || faultCode =~ /AccessError/
|
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[2].is_a?(String) && (args[1].is_a?(Integer) || (args[1].respond_to?(:to_i) && args[1].to_i != 0))
|
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,213 @@
|
|
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)
|
10
|
+
if force || !fields
|
11
|
+
@t.fields = {}
|
12
|
+
@columns_hash = {}
|
13
|
+
if session.odoo_serie < 10
|
14
|
+
fields_get = rpc_execute("fields_get", false, context)
|
15
|
+
else
|
16
|
+
fields_get = rpc_execute("fields_get", false)
|
17
|
+
end
|
18
|
+
fields_get.each { |k, field| reload_field_definition(k, field) }
|
19
|
+
@t.associations_keys = many2one_associations.keys + one2many_associations.keys + many2many_associations.keys + polymorphic_m2o_associations.keys
|
20
|
+
logger.debug "#{fields.size} fields loaded in model #{self.name}"
|
21
|
+
Ooor.model_registry.set_template(session.config, @t)
|
22
|
+
end
|
23
|
+
generate_accessors if fields != {} && (force || !@accessor_defined) #TODOmove in define_accessors method
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_fields
|
27
|
+
fields.merge(polymorphic_m2o_associations).merge(many2many_associations).merge(one2many_associations).merge(many2one_associations)
|
28
|
+
end
|
29
|
+
|
30
|
+
def fast_fields(options={})
|
31
|
+
fields = all_fields
|
32
|
+
fields.keys.select do |k|
|
33
|
+
fields[k]["type"] != "binary" && (options[:include_functions] || !fields[k]["function"])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def generate_accessors #TODO we should cache this is a module cached like the template, or eventually generate source code or both
|
40
|
+
fields.keys.each { |meth| define_field_method meth }
|
41
|
+
associations_keys.each { |meth| define_association_method meth }
|
42
|
+
one2many_associations.keys.each { |meth| accepts_nested_attributes_for meth } #TODO do it for m2o too
|
43
|
+
many2one_associations.keys.each do |meth|
|
44
|
+
define_association_method meth
|
45
|
+
define_m2o_association_method meth
|
46
|
+
end
|
47
|
+
(one2many_associations.keys + many2many_associations.keys).each do |meth|
|
48
|
+
define_association_method meth
|
49
|
+
alias_method "#{meth}_ids=", "#{meth}="
|
50
|
+
alias_method "#{meth.to_s.singularize}_ids=", "#{meth}="
|
51
|
+
define_x2m_ids_association_method meth
|
52
|
+
alias_method "#{meth.to_s.singularize}_ids", "#{meth}_ids"
|
53
|
+
end
|
54
|
+
@accessor_defined = true
|
55
|
+
end
|
56
|
+
|
57
|
+
def define_field_method(meth)
|
58
|
+
define_attribute_method meth
|
59
|
+
define_method meth do |*args|
|
60
|
+
get_attribute(meth, *args)
|
61
|
+
end
|
62
|
+
|
63
|
+
define_method "#{meth}=" do |*args|
|
64
|
+
set_attribute(meth, *args)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def define_association_method(meth)
|
69
|
+
define_attribute_method meth
|
70
|
+
define_method meth do |*args|
|
71
|
+
get_association(meth, *args)
|
72
|
+
end
|
73
|
+
|
74
|
+
define_method "#{meth}=" do |*args|
|
75
|
+
set_association(meth, *args)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def define_x2m_ids_association_method(meth)
|
80
|
+
define_method "#{meth}_ids" do |*args|
|
81
|
+
@associations[meth]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def define_m2o_association_method(meth)
|
86
|
+
define_method "#{meth}_id" do |*args|
|
87
|
+
if @associations[meth].is_a? Array
|
88
|
+
@associations[meth][0]
|
89
|
+
else
|
90
|
+
r = get_association(meth, *args)
|
91
|
+
r.is_a?(Ooor::Base) ? r.id : r
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def reload_field_definition(k, field)
|
97
|
+
case field['type']
|
98
|
+
when 'many2one'
|
99
|
+
many2one_associations[k] = field
|
100
|
+
when 'one2many'
|
101
|
+
one2many_associations[k] = field
|
102
|
+
when 'many2many'
|
103
|
+
many2many_associations[k] = field
|
104
|
+
when 'reference'
|
105
|
+
polymorphic_m2o_associations[k] = field
|
106
|
+
else
|
107
|
+
fields[k] = field if field['name'] != 'id'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
attr_accessor :_display_name
|
114
|
+
alias _name _display_name
|
115
|
+
|
116
|
+
def _destroy=(dummy)
|
117
|
+
@marked_for_destruction = true unless dummy.blank? || ["false", "0", 0].index(dummy)
|
118
|
+
end
|
119
|
+
|
120
|
+
def _destroy
|
121
|
+
@marked_for_destruction
|
122
|
+
end
|
123
|
+
|
124
|
+
def lazy_load(meth, *args)
|
125
|
+
@lazy = false
|
126
|
+
fields = (self.class.fast_fields + [meth]).uniq
|
127
|
+
load(rpc_execute('read', [@attributes["id"]], fields, *args || context)[0]).tap do
|
128
|
+
@lazy = false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_attribute(meth, *args)
|
133
|
+
lazy_load(meth, *args) if @lazy && @attributes["id"] && !@attributes.has_key?(meth)
|
134
|
+
if @attributes.has_key?(meth)
|
135
|
+
@attributes[meth]
|
136
|
+
elsif @attributes["id"] # if field is computed for instance
|
137
|
+
@attributes[meth] = rpc_execute('read', [@attributes["id"]], [meth], *args || context)[0][meth]
|
138
|
+
else
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def set_attribute(meth, *args)
|
144
|
+
value = sanitize_attribute(meth, args[0])
|
145
|
+
@attributes[meth] ||= nil
|
146
|
+
send("#{meth}_will_change!") unless @attributes[meth] == value
|
147
|
+
@attributes[meth] = value
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_association(meth, *args)
|
151
|
+
return @associations[meth] || :undef if @skip
|
152
|
+
lazy_load(meth, *args) if @lazy
|
153
|
+
if @loaded_associations.has_key?(meth)
|
154
|
+
@loaded_associations[meth]
|
155
|
+
elsif @associations.has_key?(meth)
|
156
|
+
@loaded_associations[meth] = relationnal_result(meth, *args)
|
157
|
+
else
|
158
|
+
if @attributes["id"]
|
159
|
+
@associations[meth] = rpc_execute('read', [@attributes["id"]], [meth], *args || context)[0][meth]
|
160
|
+
@loaded_associations[meth] = relationnal_result(meth, *args)
|
161
|
+
elsif self.class.one2many_associations.has_key?(meth) || self.class.many2many_associations.has_key?(meth)
|
162
|
+
load_x2m_association(meth, [], *args)
|
163
|
+
else
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_association(meth, *args)
|
170
|
+
value = sanitize_association(meth, args[0])
|
171
|
+
if self.class.many2one_associations.has_key?(meth) # TODO detect false positives changes for other associations too
|
172
|
+
if @associations[meth].is_a?(Array) && @associations[meth][0] == value \
|
173
|
+
|| @associations[meth] == value #\
|
174
|
+
return value
|
175
|
+
end
|
176
|
+
end
|
177
|
+
@skip = true
|
178
|
+
send("#{meth}_will_change!")
|
179
|
+
@skip = false
|
180
|
+
if value.is_a?(Ooor::Base) || value.is_a?(Array) && !value.empty? && value.all? {|i| i.is_a?(Ooor::Base)}
|
181
|
+
@loaded_associations[meth] = value
|
182
|
+
else
|
183
|
+
@loaded_associations.delete(meth)
|
184
|
+
end
|
185
|
+
@associations[meth] = value
|
186
|
+
end
|
187
|
+
|
188
|
+
# # Raise NoMethodError if the named attribute does not exist in order to preserve behavior expected by #clone.
|
189
|
+
# def attribute(name)
|
190
|
+
# key = name.to_s
|
191
|
+
# if self.class.fields.has_key?(key) #TODO check not symbols
|
192
|
+
# get_attribute(key)
|
193
|
+
# elsif self.class.associations_keys.index(key)
|
194
|
+
# get_association(key)
|
195
|
+
# else
|
196
|
+
# raise NoMethodError
|
197
|
+
# end
|
198
|
+
# end
|
199
|
+
|
200
|
+
def method_missing(method_symbol, *arguments)
|
201
|
+
self.class.reload_fields_definition(false)
|
202
|
+
if id
|
203
|
+
rpc_execute(method_symbol, [id], *arguments) #we assume that's an action
|
204
|
+
else
|
205
|
+
super
|
206
|
+
end
|
207
|
+
rescue UnknownAttributeOrAssociationError => e
|
208
|
+
e.klass = self.class
|
209
|
+
raise e
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|