commit 43bd805719d710f4330cab70fdcae0a4a1ba0986
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Fri Jul 24 19:28:10 2009 +0200

    [evol] finished modification of relations

diff --git a/bin/shadowwalker b/bin/shadowwalker
index 0c69a7c..b52e265 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -182,58 +182,103 @@ cmd.set_execution_block do |args|
     exit 2
   end
 
-  puts "COIN"
-  p args
   args.each do |mod_info|
-    mod_info =~ /^([a-zA-Z]+)(=|\+=|-=)(.*)$/
+    mod_info =~ /^([a-zA-Z]+(?::[a-zA-Z]+))(=|\+=|-=)(.*)$/
     key = $1
     op = $2
     val = $3
-    p "key: #{key}"
-    p "op: #{op}"
-    p "val: #{val}"
-
-    # TODO: manage relations (harder)
+    puts "key: #{key}"
+    puts "op: #{op}"
+    puts "val: #{val}"
 
     if key.nil?
       STDERR.puts "Syntax error in modification parameters: invalid field name" 
       exit 1
     end
-    unless item.has_attribute?(key)
-      STDERR.puts "No such field '#{key}' in object '#{obj_hdl}'" 
-      exit 2
-    end
 
-    attr_info = ActiveLdap::Base.schema.attribute(key)
-    if attr_info.read_only?
-      STDERR.puts "The field '#{key}' cannot be modified (read only), skipping."
-      next
+    if key.index(":")
+      type, field = key.split(":")
+    else
+      type = nil
+      field = key
     end
 
-    old_val = item.send(key)
-    old_val = [old_val] unless old_val.is_a? Array
+    case type
+    when nil
+      unless item.has_attribute?(field)
+        STDERR.puts "No such field '#{field}' in object '#{obj_hdl}'." 
+        exit 2
+      end
+
+      attr_info = ActiveLdap::Base.schema.attribute(field)
+      if attr_info.read_only?
+        STDERR.puts "The field '#{field}' cannot be modified (read only), skipping."
+        next
+      end
+
+      old_val = item.send(field)
+      old_val = [old_val] unless old_val.is_a? Array
+    when 'rel'
+      unless item.relations.include?(field)
+        STDERR.puts "No such relation '#{field}' for object '#{obj_hdl}'." 
+        exit 2
+      end
+
+      # XXX: should we add a new parameter to enforce read-only relations ?
+
+      # fetch remote object in relation, which will be the real 'val'
+      rel_info = item.info_for_relation(field)
+      foreign_item_list = rel_info[:foreign_klass].find(:all, val)
+      if foreign_item_list.empty?
+        STDERR.puts "Foreign item '#{val}' for relation '#{field}' not found."
+        exit 2
+      end
+      if foreign_item_list.size > 1
+        STDERR.puts "Ambiguous item '#{val}' for relation '#{field}' (#{foreign_item_list.size} possible items)"
+        exit 4
+      end
+      val = foreign_item_list.first
+    else
+      STDERR.puts "Unknown type '#{type}' for field '#{field}'."
+    end
 
-    # if val is nil or the latest value is removed, then the atribute is removed from the object
+    # if val is nil or the latest value is removed, then the atribute is removed from the object,
+    # or the association's attribute is removed from one side
     case op
     when '='
-      new_val = [val]
+      item.send(field + "=", [val])
     when '+='
-      if attr_info.single_value?
-        STDERR.puts "The field '#{key}' cannot hold more than one value."
-        exit 3
+      case type
+      when nil
+        if attr_info.single_value?
+          STDERR.puts "The field '#{field}' cannot hold more than one value."
+          exit 3
+        end
+
+        new_val = old_val << val
+        item.send(field + "=", new_val)
+      when 'rel'
+        if rel_info[:single_value]
+          STDERR.puts "The relation '#{field}' cannot hold more than one foreign item."
+          exit 3
+        end
+
+        item.send(field) << val
       end
-      new_val = old_val << val
     when '-='
-      new_val = old_val - [val]
+      case type
+      when nil
+        new_val = old_val - [val]
+      when 'rel'
+        item.send(field).delete(val)
+      end
     else
       STDERR.puts "Syntax error in modification parameters: wrong operator"
       exit 1
     end
-
-    item.send(key + "=", new_val)
   end
 
-  p item.valid?
+  puts "Modified attributes:"
   p item.modified_attributes
   begin
     item.save!
diff --git a/lib/ldap_shadows/controller.rb b/lib/ldap_shadows/controller.rb
index d90f72e..40efc30 100755
--- a/lib/ldap_shadows/controller.rb
+++ b/lib/ldap_shadows/controller.rb
@@ -92,6 +92,7 @@ module LdapShadows
 
         klass = find_klass(obj_name)
 
+        obj_relations_info = {}
         obj_rel.each_pair do |field_name, rel|
           foreign_klass = find_klass(rel[:object])
           rel[:class_name] = foreign_klass.to_s
@@ -104,7 +105,13 @@ module LdapShadows
           else
             raise "bug in '#{obj_name}' object relations (wrong type)"
           end
+
+          obj_relations_info[field_name] = {
+            :foreign_klass => foreign_klass,
+            :single_value => ActiveLdap::Base.schema.attribute(rel[:foreign_key]).single_value?
+          }
         end
+        klass.relations_info = obj_relations_info
       end
     end
   end
diff --git a/lib/ldap_shadows/object.rb b/lib/ldap_shadows/object.rb
index eaf3ec6..ed522dc 100755
--- a/lib/ldap_shadows/object.rb
+++ b/lib/ldap_shadows/object.rb
@@ -19,7 +19,7 @@
 
 module LdapShadows
   class LdapObject < ActiveLdap::Base
-    class_inheritable_accessor :handle, :presentation, :mapper
+    class_inheritable_accessor :handle, :presentation, :mapper, :relations_info
 
     ldap_mapping :prefix => '', :classes => ['top'], :scope => :sub
 
@@ -151,6 +151,12 @@ module LdapShadows
       list
     end
 
+    def info_for_relation(rel)
+      return nil unless self.relations.include?(rel)
+
+      self.relations_info[rel.to_sym]
+    end
+
     def organized_data(options = {})
       options.symbolize_keys!
       options[:expert] ||= false;
