commit a8bda2134c57b0c60a5fef6b0f4addbdb8f15757
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Wed Sep 8 03:55:15 2010 +0200

    [evol] preliminary virtual attributes support (with read only attributes support, and an example vattr) (refs #141)

diff --git a/conf/ldap_shadows/worlds/OpenLDAP_config/shadows/Administration/plugins/objects/global.rb b/conf/ldap_shadows/worlds/OpenLDAP_config/shadows/Administration/plugins/objects/global.rb
new file mode 100644
index 0000000..b2a0f3e
--- /dev/null
+++ b/conf/ldap_shadows/worlds/OpenLDAP_config/shadows/Administration/plugins/objects/global.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 :plopToto, '1.3.6.1.4.1.1466.115.121.1.15'
+
+def plopToto(always_array = true)
+  val = "#{self.olcLocalSSF} #{self.olcThreads}"
+  (always_array) ? [val] : val
+end
+
+def plopToto=(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/object.rb b/lib/ldap_shadows/elements/object.rb
index b5f479e..a5cca4f 100644
--- a/lib/ldap_shadows/elements/object.rb
+++ b/lib/ldap_shadows/elements/object.rb
@@ -30,9 +30,19 @@ module Elements
     attr_reader :parent_changed
 
     @relations_info = {}
+    @virtual_attributes = {}
 
     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
@@ -45,6 +55,43 @@ module Elements
       @latest_parent_full_handle = nil
     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}="))
+      })
+    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)
+      end
+    end
+
     def handle
       name = self[dn_attribute] || self.attributes[dn_attribute] || self.dn.to_s
       name = name.first if name.is_a? Array
@@ -104,7 +151,7 @@ module Elements
 
     def has_field?(field)
       return false if field.downcase == "objectclass"
-      has_attribute?(field)
+      has_attribute?(field) || virtual_attributes.include?(field)
     end
 
     def human_name
@@ -216,6 +263,7 @@ module Elements
         taken_attr_list += self.class.possible_attributes
       end
       taken_attr_list = taken_attr_list.uniq & attr_list
+      taken_attr_list += self.class.virtual_attributes
       obj_info = fetch_attributes_data(taken_attr_list, expert_attributes, admin_attributes)
       attr_list -= taken_attr_list
 
@@ -275,12 +323,12 @@ module Elements
         raise PreProcessingError, _("No such field '%s' in object '%s'") % [field, self.class.handle]
       end
 
-      attr_info = ActiveLdap::Base.schema.attribute(field)
-      if attr_info.read_only?
+      attr_info = self.attribute_info(field)
+      if attr_info[:read_only]
         raise PreProcessingError, _("The field '%s' cannot be modified (read only)") % field
       end
 
-      if attr_info.binary?
+      if attr_info[:binary]
         unless File.exists?(val)
           raise PreProcessingError, _("The field '%s' contains binary data, you must provide a filename instead of a direct value") % field
         end
@@ -290,7 +338,7 @@ module Elements
         rescue
           raise PreProcessingError, _("The file for the binary field '%s' cannot be read: ") % [field, $!]
         end
-      elsif attr_info.syntax.to_param == "1.3.6.1.4.1.1466.115.121.1.12"
+      elsif attr_info[:syntax] == "1.3.6.1.4.1.1466.115.121.1.12"
         if val =~ FULL_HANDLE_PATTERN
           obj_hdl = $1.downcase
           item_hdl = $2
@@ -654,19 +702,19 @@ module Elements
     end
 
     def fetch_attributes_data(attr_list, expert_attributes, admin_attributes)
-      attr_data = self.attributes.collect do |key, val|
-        if attr_list.include?(key)
-          attr_info = ActiveLdap::Base.schema.attribute(key)
-          [key, {
-             :syntax => attr_info.syntax.to_param,
+      attr_data = attr_list.collect do |attr|
+        base_info = self.attribute_info(attr)
+
+        if base_info.nil?
+          nil
+        else
+          val = self.send(attr)
+          [attr, base_info.merge({
              :value => val,
              :multiple => val.is_a?(Array),
-             :expert => expert_attributes.include?(key),
-             :admin => admin_attributes.include?(key),
-             :binary => attr_info.binary?
-           }]
-        else
-          nil
+             :expert => expert_attributes.include?(attr),
+             :admin => admin_attributes.include?(attr)
+           })]
         end
       end
       Hash[attr_data.compact]
