ooor 2.0.3 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c7013e218cfc09bfb4c0711c65904076f14f6e9d
4
+ data.tar.gz: 37e409fe1330084c5ada2b5051c92381577febe1
5
+ SHA512:
6
+ metadata.gz: 09af180037d24d0008ef92c4365fa81ca9fbb503b8f07045cb1fd18e134785ddb72b8aff170c9a2c51e136b0422b6e630bef2f534f038702b0821de92254060c
7
+ data.tar.gz: 2330653e660fd06751bc4bfd1ab79bfe9107b37ae441a2a98bb80ee3a85839046cd14146b4d86fa7bcf9e7d5b5cb5c714b70a7cf34fd08d120a0980d929c847c
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![Build Status](https://secure.travis-ci.org/akretion/ooor.png?branch=master)](http://travis-ci.org/akretion/ooor) [![Code Climate](https://codeclimate.com/github/akretion/ooor.png)](https://codeclimate.com/github/akretion/ooor)
2
+ [![Coverage Status](https://coveralls.io/repos/akretion/ooor/badge.png?branch=master)](https://coveralls.io/r/akretion/ooor?branch=master) [![Gem Version](https://badge.fury.io/rb/ooor.png)](http://badge.fury.io/rb/ooor) [![Dependency Status](https://www.versioneye.com/ruby/ooor/badge.png)](https://www.versioneye.com/ruby/ooor)
2
3
 
3
4
  [![OOOR by Akretion](https://s3.amazonaws.com/akretion/assets/ooor_by_akretion.png)](http://akretion.com)
4
5
 
data/bin/ooor CHANGED
@@ -41,6 +41,7 @@ unless ARGV.empty?
41
41
  system "stty -echo"
42
42
  password = $stdin.gets.chomp!
43
43
  system "stty echo"
44
+ password = 'admin' if password.blank?
44
45
  end
45
46
 
46
47
  else
@@ -12,6 +12,8 @@ require 'logger'
12
12
  module Ooor
13
13
  extend ActiveSupport::Autoload
14
14
  autoload :Base
15
+ autoload :Persistence
16
+ autoload :Callbacks
15
17
  autoload :Cache, 'active_support/cache'
16
18
  autoload :Serialization
17
19
  autoload :Relation
@@ -1,63 +1,53 @@
1
1
  module Ooor
2
2
  module Associations
3
3
 
4
- def many2one_id_method(rel, *arguments)
5
- if @associations[rel]
6
- @associations[rel][0]
7
- else
8
- obj = method_missing(rel.to_sym, *arguments)
9
- obj.is_a?(Base) ? obj.id : obj
4
+ # similar to ActiveRecord CollectionProxy but without lazy loading work yet
5
+ class CollectionProxy < Relation
6
+
7
+ def to_ary
8
+ to_a.dup
9
+ end
10
+ # alias_method :to_a, :to_ary
11
+
12
+ def class
13
+ Array
10
14
  end
11
- end
12
15
 
13
- def x_to_many_ids_method(rel, *arguments)
14
- if @associations[rel]
15
- @associations[rel]
16
- else
17
- method_missing(rel.to_sym, *arguments)
16
+ def is_a?(*args)
17
+ @records.is_a?(*args)
18
18
  end
19
+
20
+ def kind_of?(*args)
21
+ @records.kind_of?(*args)
22
+ end
23
+
19
24
  end
20
25
 
26
+
21
27
  # fakes associations like much like ActiveRecord according to the cached OpenERP data model
22
28
  def relationnal_result(method_name, *arguments)
23
29
  self.class.reload_fields_definition(false, object_session)
24
30
  if self.class.many2one_associations.has_key?(method_name)
25
- if @associations[method_name]
26
- rel = self.class.many2one_associations[method_name]['relation']
27
- id = @associations[method_name].is_a?(Integer) ? @associations[method_name] : @associations[method_name][0]
28
- load_association(rel, id, nil, *arguments)
31
+ if !@associations[method_name]
32
+ nil
29
33
  else
30
- false
34
+ id = @associations[method_name].is_a?(Integer) ? @associations[method_name] : @associations[method_name][0]
35
+ rel = self.class.many2one_associations[method_name]['relation']
36
+ self.class.const_get(rel).find(id, arguments.extract_options!)
31
37
  end
32
- elsif self.class.one2many_associations.has_key?(method_name)
33
- rel = self.class.one2many_associations[method_name]['relation']
34
- load_association(rel, @associations[method_name], [], *arguments)
35
- elsif self.class.many2many_associations.has_key?(method_name)
36
- rel = self.class.many2many_associations[method_name]['relation']
37
- load_association(rel, @associations[method_name], [], *arguments)
38
- elsif self.class.polymorphic_m2o_associations.has_key?(method_name)
38
+ elsif self.class.polymorphic_m2o_associations.has_key?(method_name) && @associations[method_name]
39
39
  values = @associations[method_name].split(',')
40
- load_association(values[0], values[1].to_i, nil, *arguments)
41
- else
42
- false
40
+ self.class.const_get(values[0]).find(values[1], arguments.extract_options!)
41
+ else # o2m or m2m
42
+ rel = self.class.all_fields[method_name]['relation']
43
+ load_x2m_association(rel, @associations[method_name], *arguments)
43
44
  end
44
45
  end
45
46
 
46
- def load_association(model_key, ids, substitute=nil, *arguments)
47
+ def load_x2m_association(model_key, ids, *arguments)
47
48
  options = arguments.extract_options!
48
49
  related_class = self.class.const_get(model_key)
49
- fields = options[:fields] || options[:only] || nil
50
- context = options[:context] || object_session
51
- (related_class.send(:find, ids, fields: fields, context: context) || substitute).tap do |r|
52
- #TODO the following is a hack to minimally mimic the CollectionProxy of Rails 3.1+; this should probably be re-implemented
53
- def r.association=(association)
54
- @association = association
55
- end
56
- r.association = related_class
57
- def r.build(attrs={})
58
- @association.new(attrs)
59
- end
60
- end
50
+ CollectionProxy.new(related_class, {}).apply_finder_options(options.merge(ids: ids))
61
51
  end
62
52
 
63
53
  end
@@ -1,27 +1,34 @@
1
1
  # OOOR: OpenObject On Ruby
2
- # Copyright (C) 2009-2013 Akretion LTDA (<http://www.akretion.com>).
2
+ # Copyright (C) 2009-2014 Akretion LTDA (<http://www.akretion.com>).
3
3
  # Author: Raphaël Valyi
4
4
  # Licensed under the MIT license, see MIT-LICENSE file
5
5
 
6
- require 'active_support/core_ext/hash/indifferent_access'
7
6
  require 'active_support/core_ext/module/delegation.rb'
7
+ require 'active_model/attribute_methods'
8
+ require 'active_model/dirty'
8
9
  require 'ooor/reflection'
9
10
  require 'ooor/reflection_ooor'
11
+ require 'ooor/errors'
10
12
 
11
13
  module Ooor
12
- class ModelTemplate #meta data shared across sessions
13
- TEMPLATE_PROPERTIES = [:openerp_id, :info, :access_ids, :description,
14
+
15
+ # meta data shared across sessions, a cache of the data in ir_model in OpenERP.
16
+ # reused accross workers in a multi-process web app (via memcache for instance).
17
+ class ModelTemplate
18
+
19
+ TEMPLATE_PROPERTIES = [:name, :openerp_id, :info, :access_ids, :description,
14
20
  :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
21
+ :many2one_associations, :one2many_associations, :many2many_associations,
22
+ :polymorphic_m2o_associations, :associations_keys,
23
+ :associations, :columns]
19
24
 
25
+ attr_accessor *TEMPLATE_PROPERTIES, :columns_hash
26
+ end
20
27
 
28
+ # the base class for proxies to OpenERP objects
21
29
  class Base < Ooor::MiniActiveResource
22
- #PREDEFINED_INHERITS = {'product.product' => 'product_tmpl_id'}
23
- include Naming, TypeCasting, Serialization, ReflectionOoor, Reflection, Associations, Report, FinderMethods, FieldMethods
24
-
30
+ include Naming, TypeCasting, Serialization, ReflectionOoor, Reflection
31
+ include Associations, Report, FinderMethods, FieldMethods
25
32
 
26
33
  # ********************** class methods ************************************
27
34
  class << self
@@ -71,6 +78,8 @@ module Ooor
71
78
  def limit(value); relation.limit(value); end
72
79
  def order(value); relation.order(value); end
73
80
  def offset(value); relation.offset(value); end
81
+ def first(*args); relation.first(*args); end
82
+ def last(*args); relation.last(*args); end
74
83
 
75
84
  def logger; Ooor.logger; end
76
85
 
@@ -82,90 +91,13 @@ module Ooor
82
91
 
83
92
  attr_accessor :associations, :loaded_associations, :ir_model_data_id, :object_session
84
93
 
94
+ include Persistence, Callbacks, ActiveModel::Dirty
95
+
85
96
  def rpc_execute(method, *args)
86
97
  args += [self.class.connection.connection_session.merge(object_session)] unless args[-1].is_a? Hash
87
98
  self.class.object_service(:execute, self.class.openerp_model, method, *args)
88
99
  end
89
100
 
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
- if value.is_a?(Ooor::Base) || value.is_a?(Array) && value.all? {|i| i.is_a?(Ooor::Base)}
101
- @loaded_associations[skey] = value #we want the method to load the association through method missing
102
- else
103
- @associations[skey] = value
104
- end
105
- else
106
- @attributes[skey] = value || nil #don't bloat with false values
107
- end
108
- end
109
- self
110
- end
111
-
112
- #takes care of reading OpenERP default field values.
113
- def initialize(attributes = {}, default_get_list=false, context={}, persisted=false)
114
- @attributes = {}
115
- @prefix_options = {}
116
- @ir_model_data_id = attributes.delete(:ir_model_data_id)
117
- @object_session = {}
118
- @object_session = HashWithIndifferentAccess.new(context)
119
- @persisted = persisted
120
- self.class.reload_fields_definition(false, @object_session)
121
- if default_get_list == []
122
- load(attributes)
123
- else
124
- load_with_defaults(attributes, default_get_list)
125
- end
126
- end
127
-
128
- def save(context={}, reload=true)
129
- new? ? create(context, reload) : update(context, reload)
130
- rescue ValidationError => e
131
- e.extract_validation_error!(errors)
132
- return false
133
- end
134
-
135
- #compatible with the Rails way but also supports OpenERP context
136
- def create(context={}, reload=true)
137
- self.id = rpc_execute('create', to_openerp_hash, context)
138
- if @ir_model_data_id
139
- IrModelData.create(model: self.class.openerp_model,
140
- 'module' => @ir_model_data_id[0],
141
- 'name' => @ir_model_data_id[1],
142
- 'res_id' => self.id)
143
- end
144
- reload_from_record!(self.class.find(self.id, context: context)) if reload
145
- @persisted = true
146
- end
147
-
148
- def update_attributes(attributes, context={}, reload=true)
149
- load(attributes, false) && save(context, reload)
150
- end
151
-
152
- #compatible with the Rails way but also supports OpenERP context
153
- def update(context={}, reload=true) #TODO use http://apidock.com/rails/ActiveRecord/Dirty to minimize data to save back
154
- rpc_execute('write', [self.id], to_openerp_hash, context)
155
- reload_fields(context) if reload
156
- @persisted = true
157
- end
158
-
159
- #compatible with the Rails way but also supports OpenERP context
160
- def destroy(context={})
161
- rpc_execute('unlink', [self.id], context)
162
- end
163
-
164
- #OpenERP copy method, load persisted copied Object
165
- def copy(defaults={}, context={})
166
- self.class.find(rpc_execute('copy', self.id, defaults, context), context: context)
167
- end
168
-
169
101
  #Generic OpenERP rpc method call
170
102
  def call(method, *args) rpc_execute(method, *args) end
171
103
 
@@ -192,31 +124,8 @@ module Ooor
192
124
 
193
125
  private
194
126
 
195
- def load_with_defaults(attributes, default_get_list)
196
- defaults = rpc_execute("default_get", default_get_list || self.class.fields.keys + self.class.associations_keys, object_session.dup)
197
- attributes = HashWithIndifferentAccess.new(defaults.merge(attributes.reject {|k, v| v.blank? }))
198
- load(attributes)
199
- end
200
-
201
- def load_on_change_result(result, field_name, field_value)
202
- if result["warning"]
203
- self.class.logger.info result["warning"]["title"]
204
- self.class.logger.info result["warning"]["message"]
205
- end
206
- attrs = @attributes.merge(field_name => field_value)
207
- attrs.merge!(result["value"])
208
- load(attrs)
209
- end
210
-
211
127
  # Ruby 1.9.compat, See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
212
128
  def to_ary; nil; end # :nodoc:
213
129
 
214
- def reload_from_record!(record) load(record.attributes.merge(record.associations)) end
215
-
216
- def reload_fields(context)
217
- records = self.class.find(self.id, context: context, fields: @attributes.keys + @associations.keys)
218
- reload_from_record!(records)
219
- end
220
-
221
130
  end
222
131
  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
@@ -13,22 +13,17 @@ module Ooor
13
13
  fields_get = rpc_execute("fields_get", false, context)
14
14
  fields_get.each { |k, field| reload_field_definition(k, field) }
15
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
16
  logger.debug "#{fields.size} fields loaded in model #{self.name}"
23
17
  Ooor.model_registry.set_template(connection.config, @t)
24
18
  end
19
+ generate_accessors if fields != {} && (force || !@accessor_defined) #TODOmove in define_accessors method
25
20
  end
26
21
 
27
22
  def all_fields
28
23
  fields.merge(polymorphic_m2o_associations).merge(many2many_associations).merge(one2many_associations).merge(many2one_associations)
29
24
  end
30
25
 
31
- def fast_fields(options)
26
+ def fast_fields(options={})
32
27
  fields = all_fields
33
28
  fields.keys.select do |k|
34
29
  fields[k]["type"] != "binary" && (options[:include_functions] || !fields[k]["function"])
@@ -40,10 +35,12 @@ module Ooor
40
35
  unless self.respond_to?(meth)
41
36
  self.instance_eval do
42
37
  define_method "#{meth}_attributes=" do |*args|
43
- self.send :method_missing, *[meth, *args]
38
+ send("#{meth}_will_change!")
39
+ @associations[meth] = args[0]
40
+ @loaded_associations[meth] = args[0]
44
41
  end
45
42
  define_method "#{meth}_attributes" do |*args|
46
- self.send :method_missing, *[meth, *args]
43
+ @loaded_associations[meth]
47
44
  end
48
45
  end
49
46
  end
@@ -51,102 +48,153 @@ module Ooor
51
48
 
52
49
  private
53
50
 
54
- def define_field_method(meth)
55
- unless self.respond_to?(meth)
56
- self.instance_eval do
57
- define_method meth do |*args|
58
- self.send :method_missing, *[meth, *args]
59
- end
60
- end
61
- end
51
+ def generate_accessors #TODO we should cache this is a module cached like the template, or eventually generate source code or both
52
+ fields.keys.each { |meth| define_field_method meth }
53
+ associations_keys.each { |meth| define_association_method meth }
54
+ one2many_associations.keys.each { |meth| define_nested_attributes_method meth }
55
+ many2one_associations.keys.each do |meth|
56
+ define_association_method meth
57
+ define_m2o_association_method meth
62
58
  end
59
+ (one2many_associations.keys + many2many_associations.keys).each do |meth|
60
+ define_association_method meth
61
+ alias_method "#{meth}_ids", meth
62
+ alias_method "#{meth}_ids=", "#{meth}="
63
+ alias_method "#{meth.to_s.singularize}_ids", meth
64
+ alias_method "#{meth.to_s.singularize}_ids=", "#{meth}="
65
+ end
66
+ @accessor_defined = true
67
+ end
63
68
 
64
- def reload_field_definition(k, field)
65
- case field['type']
66
- when 'many2one'
67
- many2one_associations[k] = field
68
- when 'one2many'
69
- one2many_associations[k] = field
70
- when 'many2many'
71
- many2many_associations[k] = field
72
- when 'reference'
73
- polymorphic_m2o_associations[k] = field
74
- else
75
- fields[k] = field if field['name'] != 'id'
76
- end
69
+ def define_field_method(meth)
70
+ define_attribute_methods meth
71
+ define_method meth do |*args|
72
+ get_attribute(meth, *args)
77
73
  end
78
- end
79
74
 
80
- def method_missing(method_symbol, *arguments)
81
- method_name = method_symbol.to_s
82
- method_key = method_name.sub('=', '')
83
- self.class.reload_fields_definition(false, object_session)
84
- if attributes.has_key?(method_key)
85
- if method_name.end_with?('=')
86
- attributes[method_key] = arguments[0]
87
- else
88
- attributes[method_key]
75
+ define_method "#{meth}=" do |*args|
76
+ set_attribute(meth, *args)
89
77
  end
90
- elsif @loaded_associations.has_key?(method_name)
91
- @loaded_associations[method_name]
92
- elsif @associations.has_key?(method_name)
93
- result = relationnal_result(method_name, *arguments)
94
- @loaded_associations[method_name] = result and return result if result
95
- elsif method_name.end_with?('=')
96
- return method_missing_value_assign(method_key, arguments)
97
- elsif self.class.fields.has_key?(method_name) || self.class.associations_keys.index(method_name) #unloaded field/association
98
- return lazzy_load_field(method_name, *arguments)
99
- # check if that is not a Rails style association with an _id[s][=] suffix:
100
- elsif method_name.match(/_id$/) && self.class.associations_keys.index(rel=method_name.gsub(/_id$/, ""))
101
- return many2one_id_method(rel, *arguments)
102
- elsif method_name.match(/_ids$/) && self.class.associations_keys.index(rel=method_name.gsub(/_ids$/, ""))
103
- return x_to_many_ids_method(rel, *arguments)
104
- elsif id
105
- rpc_execute(method_key, [id], *arguments) #we assume that's an action
106
- else
107
- super
108
78
  end
109
79
 
110
- rescue UnknownAttributeOrAssociationError => e
111
- e.klass = self.class
112
- raise e
113
- end
80
+ def define_association_method(meth)
81
+ define_attribute_methods meth
82
+ define_method meth do |*args|
83
+ get_association(meth, *args)
84
+ end
114
85
 
115
- private
86
+ define_method "#{meth}=" do |*args|
87
+ set_association(meth, *args)
88
+ end
89
+ end
116
90
 
117
- def method_missing_value_assign(method_key, arguments)
118
- if is_association_assignment(method_key)
119
- @associations[method_key] = arguments[0]
120
- @loaded_associations[method_key] = arguments[0]
121
- elsif is_attribute_assignment(method_key)
122
- @attributes[method_key] = arguments[0]
91
+ def define_m2o_association_method(meth)
92
+ define_method "#{meth}_id" do |*args|
93
+ if @associations[meth].is_a? Array
94
+ @associations[meth][0]
95
+ else
96
+ r = get_association(meth, *args)
97
+ r.is_a?(Ooor::Base) ? r.id : r
98
+ end
123
99
  end
124
100
  end
125
101
 
126
- def is_association_assignment(method_key)
127
- (self.class.associations_keys + self.class.many2one_associations.collect do |k, field|
128
- klass = self.class.const_get(field['relation'])
129
- klass.reload_fields_definition(false, object_session)
130
- klass.t.associations_keys
131
- end.flatten).index(method_key)
102
+ def reload_field_definition(k, field)
103
+ case field['type']
104
+ when 'many2one'
105
+ many2one_associations[k] = field
106
+ when 'one2many'
107
+ one2many_associations[k] = field
108
+ when 'many2many'
109
+ many2many_associations[k] = field
110
+ when 'reference'
111
+ polymorphic_m2o_associations[k] = field
112
+ else
113
+ fields[k] = field if field['name'] != 'id'
114
+ end
132
115
  end
133
116
 
134
- def is_attribute_assignment(method_key)
135
- (self.class.fields.keys + self.class.many2one_associations.collect do |k, field|
136
- klass = self.class.const_get(field['relation'])
137
- klass.reload_fields_definition(false, object_session)
138
- klass.t.fields.keys
139
- end.flatten).index(method_key)
117
+ end
118
+
119
+ def get_attribute(meth, *args)
120
+ if @attributes.has_key?(meth)
121
+ @attributes[meth]
122
+ else #lazy loading
123
+ if @attributes["id"]
124
+ @attributes[meth] = rpc_execute('read', [@attributes["id"]], [meth], *args || object_session)[0][meth]
125
+ else
126
+ nil
127
+ end
140
128
  end
129
+ end
130
+
131
+ def set_attribute(meth, *args)
132
+ value = sanitize_attribute(meth, args[0])
133
+ @attributes[meth] ||= nil
134
+ send("#{meth}_will_change!") unless @attributes[meth] == value
135
+ @attributes[meth] = value
136
+ end
141
137
 
142
- def lazzy_load_field(field_name, *arguments)
143
- if attributes["id"]
144
- load(rpc_execute('read', [id], [field_name], *arguments || object_session)[0] || {})
145
- method_missing(field_name, *arguments)
138
+ def get_association(meth, *args)
139
+ return @associations[meth] || :undef if @skip
140
+ if @loaded_associations.has_key?(meth)
141
+ @loaded_associations[meth]
142
+ elsif @associations.has_key?(meth)
143
+ @loaded_associations[meth] = relationnal_result(meth, *args)
144
+ else
145
+ if @attributes["id"]
146
+ @associations[meth] = rpc_execute('read', [@attributes["id"]], [meth], *args || object_session)[0][meth]
147
+ @loaded_associations[meth] = relationnal_result(meth, *args)
148
+ elsif self.class.one2many_associations.has_key?(meth) || self.class.many2many_associations.has_key?(meth)
149
+ load_x2m_association(self.class.all_fields[meth]['relation'], [], *args)
146
150
  else
147
151
  nil
148
152
  end
149
153
  end
154
+ end
155
+
156
+ def set_association(meth, *args)
157
+ value = sanitize_association(meth, args[0])
158
+ if self.class.many2one_associations.has_key?(meth) # TODO detect false positives changes for other associations too
159
+ if @associations[meth].is_a?(Array) && @associations[meth][0] == value \
160
+ || @associations[meth] == value #\
161
+ return value
162
+ end
163
+ end
164
+ @skip = true
165
+ send("#{meth}_will_change!")
166
+ @skip = false
167
+ if value.is_a?(Ooor::Base) || value.is_a?(Array) && !value.empty? && value.all? {|i| i.is_a?(Ooor::Base)}
168
+ @loaded_associations[meth] = value
169
+ else
170
+ @loaded_associations.delete(meth)
171
+ end
172
+ @associations[meth] = value
173
+ end
174
+
175
+ # # Raise NoMethodError if the named attribute does not exist in order to preserve behavior expected by #clone.
176
+ # def attribute(name)
177
+ # key = name.to_s
178
+ # if self.class.fields.has_key?(key) #TODO check not symbols
179
+ # get_attribute(key)
180
+ # elsif self.class.associations_keys.index(key)
181
+ # get_association(key)
182
+ # else
183
+ # raise NoMethodError
184
+ # end
185
+ # end
186
+
187
+ def method_missing(method_symbol, *arguments)
188
+ self.class.reload_fields_definition(false, object_session)
189
+ if id
190
+ rpc_execute(method_symbol, [id], *arguments) #we assume that's an action
191
+ else
192
+ super
193
+ end
194
+ rescue UnknownAttributeOrAssociationError => e
195
+ e.klass = self.class
196
+ raise e
197
+ end
150
198
 
151
199
  end
152
200
  end