ooor 2.0.3 → 2.0.4
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/README.md +1 -0
- data/bin/ooor +1 -0
- data/lib/ooor.rb +2 -0
- data/lib/ooor/associations.rb +30 -40
- data/lib/ooor/base.rb +22 -113
- data/lib/ooor/callbacks.rb +18 -0
- data/lib/ooor/field_methods.rb +133 -85
- data/lib/ooor/helpers/core_helpers.rb +9 -63
- data/lib/ooor/mini_active_resource.rb +0 -18
- data/lib/ooor/naming.rb +2 -2
- data/lib/ooor/persistence.rb +140 -0
- data/lib/ooor/railtie.rb +2 -1
- data/lib/ooor/reflection.rb +11 -399
- data/lib/ooor/reflection_ooor.rb +36 -11
- data/lib/ooor/relation.rb +48 -23
- data/lib/ooor/type_casting.rb +108 -75
- data/lib/ooor/version.rb +1 -1
- data/spec/ooor_spec.rb +133 -27
- metadata +35 -40
    
        checksums.yaml
    ADDED
    
    | @@ -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 | 
             
            [](http://travis-ci.org/akretion/ooor) [](https://codeclimate.com/github/akretion/ooor)
         | 
| 2 | 
            +
            [](https://coveralls.io/r/akretion/ooor?branch=master) [](http://badge.fury.io/rb/ooor) [](https://www.versioneye.com/ruby/ooor)
         | 
| 2 3 |  | 
| 3 4 | 
             
            [](http://akretion.com)
         | 
| 4 5 |  | 
    
        data/bin/ooor
    CHANGED
    
    
    
        data/lib/ooor.rb
    CHANGED
    
    
    
        data/lib/ooor/associations.rb
    CHANGED
    
    | @@ -1,63 +1,53 @@ | |
| 1 1 | 
             
            module Ooor
         | 
| 2 2 | 
             
              module Associations
         | 
| 3 3 |  | 
| 4 | 
            -
                 | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
                   | 
| 8 | 
            -
                     | 
| 9 | 
            -
             | 
| 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 | 
            -
             | 
| 14 | 
            -
             | 
| 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  | 
| 26 | 
            -
                       | 
| 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 | 
            -
                       | 
| 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. | 
| 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 | 
            -
                     | 
| 41 | 
            -
                  else
         | 
| 42 | 
            -
                     | 
| 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  | 
| 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 | 
            -
                   | 
| 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
         | 
    
        data/lib/ooor/base.rb
    CHANGED
    
    | @@ -1,27 +1,34 @@ | |
| 1 1 | 
             
            #    OOOR: OpenObject On Ruby
         | 
| 2 | 
            -
            #    Copyright (C) 2009- | 
| 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 | 
            -
             | 
| 13 | 
            -
             | 
| 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, | 
| 16 | 
            -
                  : | 
| 17 | 
            -
             | 
| 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 | 
            -
                 | 
| 23 | 
            -
                include  | 
| 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
         | 
    
        data/lib/ooor/field_methods.rb
    CHANGED
    
    | @@ -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 | 
            -
                           | 
| 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 | 
            -
                           | 
| 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 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 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 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 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 | 
            -
             | 
| 81 | 
            -
             | 
| 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 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 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 | 
            -
             | 
| 86 | 
            +
                    define_method "#{meth}=" do |*args|
         | 
| 87 | 
            +
                      set_association(meth, *args)
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
                  end
         | 
| 116 90 |  | 
| 117 | 
            -
                  def  | 
| 118 | 
            -
                     | 
| 119 | 
            -
                      @associations[ | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 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  | 
| 127 | 
            -
                     | 
| 128 | 
            -
             | 
| 129 | 
            -
                       | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 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 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 139 | 
            -
             | 
| 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 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
             | 
| 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
         |