commit c2c269539e77e757f52a43ef19de5c5c6f85ae1e
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Wed Sep 8 19:58:10 2010 +0200

    [evol] virtual attributes support §2 (aspect vattrs with example) (refs #141)

diff --git a/conf/ldap_shadows/worlds/OpenLDAP_config/shadows/Administration/plugins/aspects/database_hdb.rb b/conf/ldap_shadows/worlds/OpenLDAP_config/shadows/Administration/plugins/aspects/database_hdb.rb
new file mode 100644
index 0000000..ccfbf66
--- /dev/null
+++ b/conf/ldap_shadows/worlds/OpenLDAP_config/shadows/Administration/plugins/aspects/database_hdb.rb
@@ -0,0 +1,15 @@
+
+# should methods be generated by metaprogramming ?
+# (to ensure type_cast/normalize_value and not to repeat attr parameters)
+
+ldap_virtual_attr :plopToto2, '1.3.6.1.4.1.1466.115.121.1.15'
+
+def plopToto2(always_array = true)
+  val = "#{self.olcMaxDerefDepth} #{self.olcDbMode}"
+  (always_array) ? [val] : val
+end
+
+def plopToto2=(val)
+  pp self.schema.ldap_syntax('1.3.6.1.4.1.1466.115.121.1.15').normalize_value(367)
+end
+
diff --git a/lib/ldap_shadows/elements.rb b/lib/ldap_shadows/elements.rb
index b496184..6378c9d 100644
--- a/lib/ldap_shadows/elements.rb
+++ b/lib/ldap_shadows/elements.rb
@@ -39,6 +39,40 @@ module LdapShadows
           @cast_rel_done = true
         end
 
+        def virtual_attributes
+          (@virtual_attributes || {}).keys
+        end
+
+        def ldap_virtual_attr(attr, syntax, single_value = false, binary = false)
+          @virtual_attributes ||= {}
+          @virtual_attributes[attr.to_s] = {
+             :syntax => syntax,
+             :single_value => single_value,
+             :binary => binary
+          }
+        end
+
+        def real_attribute_info(attr)
+          attr_info = ActiveLdap::Base.schema.attribute(attr)
+          return if attr_info.nil?
+
+          {
+             :syntax => attr_info.syntax.to_param,
+             :single_value => attr_info.single_value?,
+             :read_only => attr_info.read_only?,
+             :binary => attr_info.binary?
+          }
+        end
+
+        def virtual_attribute_info(attr)
+          info = (@virtual_attributes || {})[attr.to_s]
+          return if info.nil?
+
+          info.dup.merge({
+             :read_only => (not self.instance_methods.include?("#{attr}="))
+          })
+        end
+
         # default empty hooks
         def hook_before_create(item); end
         def hook_before_modify(item); end
diff --git a/lib/ldap_shadows/elements/aspect.rb b/lib/ldap_shadows/elements/aspect.rb
index 712dcaf..9b9e493 100644
--- a/lib/ldap_shadows/elements/aspect.rb
+++ b/lib/ldap_shadows/elements/aspect.rb
@@ -16,12 +16,22 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #++
 
+require 'delegate'
 
 module LdapShadows
 module Elements
-  class LdapAspect
+  class LdapAspect < Delegator
     include LdapElement
 
+    def initialize(obj)
+      super
+      @_sd_obj = obj
+    end
+
+    def __getobj__
+      @_sd_obj
+    end
+
     def self.cast_relations
       super
 
diff --git a/lib/ldap_shadows/elements/object.rb b/lib/ldap_shadows/elements/object.rb
index 85f2a79..eaffd6f 100644
--- a/lib/ldap_shadows/elements/object.rb
+++ b/lib/ldap_shadows/elements/object.rb
@@ -34,15 +34,6 @@ module Elements
 
     class << self
       attr_reader :relations_info
-
-      def virtual_attributes
-        (@virtual_attributes || {}).keys
-      end
-    end
-
-    def virtual_attributes
-      # TODO: add aspects attributes
-      self.class.virtual_attributes
     end
 
     # default
@@ -58,42 +49,26 @@ module Elements
 
       @parent_changed = false
       @latest_parent_full_handle = nil
+      @aspects_inst_cache = {}
     end
 
-    def self.ldap_virtual_attr(attr, syntax, single_value = false, binary = false)
-      @virtual_attributes ||= {}
-      @virtual_attributes[attr.to_s] = {
-         :syntax => syntax,
-         :single_value => single_value,
-         :binary => binary
-      }
-    end
-
-    def self.real_attribute_info(attr)
-      attr_info = ActiveLdap::Base.schema.attribute(attr)
-      return if attr_info.nil?
-
-      {
-         :syntax => attr_info.syntax.to_param,
-         :single_value => attr_info.single_value?,
-         :read_only => attr_info.read_only?,
-         :binary => attr_info.binary?
-      }
-    end
-
-    def self.virtual_attribute_info(attr)
-      info = @virtual_attributes[attr.to_s].dup
-      info.merge({
-         :read_only => (not self.instance_methods.include?("#{attr}="))
-      })
+    def virtual_attributes
+      list = self.class.virtual_attributes
+      aspects.values.each do |aspect|
+        list += aspect.virtual_attributes
+      end
     end
 
     # return nil if not found
     def attribute_info(attr)
-      if self.has_attribute? attr
-        self.class.real_attribute_info(attr)
-      else
-        self.class.virtual_attribute_info(attr)
+      return self.class.real_attribute_info(attr) if self.has_attribute? attr
+
+      info = self.class.virtual_attribute_info(attr)
+      return info unless info.nil?
+
+      aspects.values.each do |aspect|
+        info = aspect.virtual_attribute_info(attr)
+        return info unless info.nil?
       end
     end
 
@@ -156,7 +131,12 @@ module Elements
 
     def has_field?(field)
       return false if field.downcase == "objectclass"
-      has_attribute?(field) || virtual_attributes.include?(field)
+      return true if has_attribute?(field)
+      return true if virtual_attributes.include?(field)
+      aspects.values.each do |aspect|
+        return true if aspect.virtual_attributes.include?(field)
+      end
+      return false
     end
 
     def human_name
@@ -285,6 +265,7 @@ module Elements
       unless attr_list.empty?
         self.aspects.values.each do |aspect|
           taken_attr_list = (aspect.possible_attributes & attr_list)
+          taken_attr_list += aspect.virtual_attributes
           obj_aspects[aspect.handle] ||= {}
           obj_aspects[aspect.handle].merge!(fetch_attributes_data(taken_attr_list, expert_attributes, admin_attributes))
           attr_list -= taken_attr_list
@@ -551,6 +532,21 @@ module Elements
       r
     end
 
+    def method_missing(method, *args)
+      aspects.each_pair do |aspect_name, aspect|
+        if aspect.instance_methods.include? method.to_s
+          aspect_inst = @aspects_inst_cache[aspect_name]
+          if aspect_inst.nil?
+            aspect_inst = aspect.new(self)
+            @aspects_inst_cache[aspect_name] = aspect_inst
+          end
+          return aspect_inst.send(method, *args)
+        end
+      end
+
+      super
+    end
+
     protected
 
     def before_save_jobs
