Kurt Stephens

Nerd Up!

Recent comments

Syndicate

Syndicate content

Browse archives

« January 2009  
Mo Tu We Th Fr Sa Su
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

How to duplicate DataMapper objects

Kurt on Sun, 2008-11-16 21:04.

How to create a deep clone of a DataMapper::Resource object so it can be stored in a different repository.

This does not work:

obj = Foo.first
obj = obj.dup
repository(:other) { obj.save }

Define DataMapper::Duplicate:

module DataMapper
  module Duplicate
    def duplicate_inner dup_map, opts = nil
      # Recursion lock.
      return obj if obj = dup_map[self.object_id]

      props = { }

      # Get all non-key properties.
      self.class.properties(repository.name).each do | property |
        next if property.key?
        props[property.name] = send(property.name)
      end

      # Remove properties that might be FKs.
      self.class.many_to_one_relationships.each do | relationship |
        relationship.child_key.each do | property |
          props.delete(property.name)
        end
      end

      # Merge in any overrides.
      props = props.merge opts if opts

      # Create a new object.
      obj = self.class.new(props)

      # Prevent recursion.
      dup_map[self.object_id] = obj

      # Give objects a chance to deepen themselves.
      obj.duplicate_deepen(dup_map)

      # Deepen child relationships.
      self.class.relationships.each do | (name, relationship) |
        parent_model = relationship.instance_variable_get(:@parent_model)
        next unless parent_model == self.class || parent_model == self.class.name

        # Get current value.
        name = relationship.name
        value = send(name)

        # Handle proxies.
        case value
        when DataMapper::Associations::OneToMany::Proxy, 
             DataMapper::Associations::ManyToMany::Proxy,
             Enumerable
          obj_assoc = obj.send(name)
          value.each do | value |
            value = value.duplicate_inner(dup_map) if value.respond_to?(:duplicate_inner)
            obj_assoc << value
          end
        else
          value = value.duplicate_inner(dup_map) if value.respond_to?(:duplicate_inner)
          obj.send("#{name}=", value)
        end
      end

      obj
    end

    def duplicate opts = nil
      duplicate_inner({ }, opts)
    end

    def duplicate_deepen dup_map
      self
    end
  end

  module Resource
    include Duplicate
  end
end

With DataMapper::Duplicate

obj = Foo.first
obj = obj.duplicate
repository(:other) { obj.save }

Whee!


links: Kurt's blog | add new comment | 210 reads