Skip to content

Instantly share code, notes, and snippets.

@dkubb
Created March 17, 2010 23:52

Revisions

  1. dkubb revised this gist Apr 6, 2010. 1 changed file with 6 additions and 10 deletions.
    16 changes: 6 additions & 10 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -173,13 +173,13 @@ def initialize(*)
    before_hooks.unshift BeforeHook.new(resource, 'before_save_hook')
    after_hooks.push AfterHook.new(resource, 'after_save_hook')
    end
    end

    class Create < Save
    def call
    resource.send(:_create)
    resource.persisted_state = resource.persisted_state.commit
    end
    end

    class Create < Save
    def add_to_session(session)
    super

    @@ -195,11 +195,7 @@ def add_to_session(session)
    end
    end

    class Update < Save
    def call
    resource.send(:_update)
    end
    end
    class Update < Save; end

    class Destroy < Command
    include HookableCommand
    @@ -210,7 +206,7 @@ def initialize(*)
    end

    def call
    resource.send(:_destroy, false)
    resource.persisted_state = resource.persisted_state.delete.commit
    end
    end

    @@ -223,7 +219,7 @@ def initialize(resource, relationship)
    end

    def call
    relationship.set(resource, relationship.get!(resource))
    resource.__send__("#{relationship.name}=", relationship.get(resource))
    end

    def ==(other)
  2. dkubb revised this gist Apr 6, 2010. 1 changed file with 3 additions and 9 deletions.
    12 changes: 3 additions & 9 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -87,7 +87,7 @@ class Command

    def initialize(resource)
    @resource = resource
    @parents = resource.send(:parent_resources).map do |parent|
    @parents = resource.send(:parent_associations).map do |parent|
    parent.save_command
    end
    end
    @@ -333,13 +333,13 @@ def save
    return if session.include?(self)

    # add parents to the UoW
    parent_resources.each { |parent| parent.save }
    parent_associations.each { |parent| parent.save }

    # add resource to the UoW
    session << self

    # add children to the UoW
    child_resources.each { |child| child.save }
    child_associations.flatten.each { |child| child.save }
    end
    end

    @@ -351,12 +351,6 @@ def save_command
    Session::Save.new(self)
    end

    private

    def child_resources
    child_collections.flatten
    end

    end
    end

  3. dkubb revised this gist Mar 22, 2010. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion test.rb
    Original file line number Diff line number Diff line change
    @@ -204,6 +204,11 @@ def call
    class Destroy < Command
    include HookableCommand

    def initialize(*)
    super
    @parents.clear # XXX: hack, reset what the parent class sets
    end

    def call
    resource.send(:_destroy, false)
    end
    @@ -396,7 +401,7 @@ class Person
    parent.save

    puts '-' * 80
    #parent.children.destroy
    parent.children.destroy
    parent.destroy

    __END__
  4. dkubb revised this gist Mar 20, 2010. 1 changed file with 21 additions and 21 deletions.
    42 changes: 21 additions & 21 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -403,45 +403,45 @@ class Person

    OUTPUT:

    ~ (0.000168) SELECT sqlite_version(*)
    ~ (0.000186) DROP TABLE IF EXISTS "people"
    ~ (0.000030) PRAGMA table_info("people")
    ~ (0.000476) CREATE TABLE "people" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "name" VARCHAR(50) NOT NULL, "parent_id" INTEGER)
    ~ (0.000132) CREATE INDEX "index_people_parent" ON "people" ("parent_id")
    ~ (0.000127) CREATE UNIQUE INDEX "unique_people_name" ON "people" ("name")
    ~ (0.000151) SELECT sqlite_version(*)
    ~ (0.000179) DROP TABLE IF EXISTS "people"
    ~ (0.000025) PRAGMA table_info("people")
    ~ (0.000457) CREATE TABLE "people" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "name" VARCHAR(50) NOT NULL, "parent_id" INTEGER)
    ~ (0.000146) CREATE INDEX "index_people_parent" ON "people" ("parent_id")
    ~ (0.000128) CREATE UNIQUE INDEX "unique_people_name" ON "people" ("name")
    --------------------------------------------------------------------------------
    Before Saving Dan Kubb
    Before Creating Dan Kubb
    ~ (0.000116) INSERT INTO "people" ("name") VALUES ('Dan Kubb')
    After Creating Dan Kubb
    After Saving Dan Kubb
    Before Saving Alex Kubb
    Before Creating Alex Kubb
    ~ (0.000069) INSERT INTO "people" ("name", "parent_id") VALUES ('Alex Kubb', 1)
    After Creating Alex Kubb
    After Saving Alex Kubb
    Before Saving Katie Kubb
    Before Creating Katie Kubb
    ~ (0.000062) INSERT INTO "people" ("name", "parent_id") VALUES ('Katie Kubb', 1)
    ~ (0.000107) INSERT INTO "people" ("name") VALUES ('Dan Kubb')
    ~ (0.000063) INSERT INTO "people" ("name", "parent_id") VALUES ('Alex Kubb', 1)
    ~ (0.000055) INSERT INTO "people" ("name", "parent_id") VALUES ('Katie Kubb', 1)
    After Creating Dan Kubb
    After Saving Dan Kubb
    After Creating Alex Kubb
    After Saving Alex Kubb
    After Creating Katie Kubb
    After Saving Katie Kubb
    --------------------------------------------------------------------------------
    Before Saving Barbara-Ann Kubb
    Before Updating Barbara-Ann Kubb
    ~ (0.000127) UPDATE "people" SET "name" = 'Barbara-Ann Kubb' WHERE "id" = 1
    After Updating Barbara-Ann Kubb
    After Saving Barbara-Ann Kubb
    Before Saving Alexander Kubb
    Before Updating Alexander Kubb
    ~ (0.000073) UPDATE "people" SET "name" = 'Alexander Kubb' WHERE "id" = 2
    After Updating Alexander Kubb
    After Saving Alexander Kubb
    Before Saving Katherine Kubb
    Before Updating Katherine Kubb
    ~ (0.000087) UPDATE "people" SET "name" = 'Katherine Kubb' WHERE "id" = 3
    ~ (0.000123) UPDATE "people" SET "name" = 'Barbara-Ann Kubb' WHERE "id" = 1
    ~ (0.000054) UPDATE "people" SET "name" = 'Alexander Kubb' WHERE "id" = 2
    ~ (0.000052) UPDATE "people" SET "name" = 'Katherine Kubb' WHERE "id" = 3
    After Updating Barbara-Ann Kubb
    After Saving Barbara-Ann Kubb
    After Updating Alexander Kubb
    After Saving Alexander Kubb
    After Updating Katherine Kubb
    After Saving Katherine Kubb
    --------------------------------------------------------------------------------
    Before Destroying Barbara-Ann Kubb
    ~ (0.000136) DELETE FROM "people" WHERE "id" = 1
    ~ (0.000064) DELETE FROM "people" WHERE "id" = 1
    After Destroying Barbara-Ann Kubb
  5. dkubb revised this gist Mar 20, 2010. 1 changed file with 16 additions and 26 deletions.
    42 changes: 16 additions & 26 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@

    module DataMapper
    class Session
    attr_reader :dependencies
    attr_reader :before_hooks, :dependencies, :after_hooks

    def self.scope
    original = Thread.current[:dm_session]
    @@ -34,7 +34,9 @@ def self.scope
    end

    def initialize(&block)
    @before_hooks = CommandDependencies.new
    @dependencies = CommandDependencies.new
    @after_hooks = CommandDependencies.new
    end

    def valid?
    @@ -66,7 +68,9 @@ def destroy(resource)
    end

    def commit
    before_hooks.call
    dependencies.call
    after_hooks.call
    freeze
    end

    @@ -115,8 +119,6 @@ def add_to_session(session)
    end

    module HookableCommand
    attr_reader :before_hooks, :after_hooks

    def initialize(*)
    super

    @@ -125,39 +127,26 @@ def initialize(*)
    end

    def add_to_session(session)
    add_before_hook_dependencies(session)
    add_hook_dependencies(session, :before_hooks)
    super
    add_after_hook_dependencies(session)
    add_hook_dependencies(session, :after_hooks)
    end

    private

    def add_before_hook_dependencies(session)
    # make before hooks dependent on the parent(s) before hooks
    parents.each do |parent|
    next unless parent.respond_to?(:before_hooks)
    before_hooks.each { |hook| hook.parents.concat(parent.before_hooks) }
    end

    # make current command dependent on before_hook
    parents.concat(before_hooks)
    attr_reader :before_hooks, :after_hooks

    # add before hooks to dependencies
    session.dependencies.concat(before_hooks)
    end
    def add_hook_dependencies(session, name)
    hooks = send(name)

    def add_after_hook_dependencies(session)
    # make after hooks dependent on the parent(s) after hooks
    # make hooks dependent on the parent(s) hooks to ensure they
    # are executed in the same order as the parent commands
    parents.each do |parent|
    next unless parent.respond_to?(:after_hooks)
    after_hooks.each { |hook| hook.parents.concat(parent.after_hooks) }
    hooks.each { |hook| hook.parents.concat(parent.send(name)) }
    end

    # make after hooks dependent on curent command
    after_hooks.each { |hook| hook.parents << self }

    # add after hooks to dependencies
    session.dependencies.concat(after_hooks)
    # add hooks to dependencies
    session.send(name).concat(hooks)
    end

    def command_name
    @@ -248,6 +237,7 @@ class Hook < Command
    def initialize(resource, name)
    super(resource)
    @name = name
    @parents.clear # XXX: hack, reset what the parent class sets
    end

    def call
  6. dkubb revised this gist Mar 20, 2010. 1 changed file with 15 additions and 15 deletions.
    30 changes: 15 additions & 15 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -43,7 +43,7 @@ def valid?

    def <<(resource)
    if command = resource.save_command
    command.add_to_dependencies(dependencies)
    command.add_to_session(self)
    else
    # TODO: remove from the dependencies list
    # - would need to remove *all* references from all commands
    @@ -61,7 +61,7 @@ def include?(resource)
    end

    def destroy(resource)
    Destroy.new(resource).add_to_dependencies(dependencies)
    Destroy.new(resource).add_to_session(self)
    self
    end

    @@ -108,8 +108,8 @@ def hash
    resource.object_id.hash
    end

    def add_to_dependencies(dependencies)
    dependencies << self
    def add_to_session(session)
    session.dependencies << self
    end

    end
    @@ -124,15 +124,15 @@ def initialize(*)
    @after_hooks = [ AfterHook.new(resource, "after_#{command_name}_hook") ]
    end

    def add_to_dependencies(dependencies)
    add_before_hook_dependencies(dependencies)
    def add_to_session(session)
    add_before_hook_dependencies(session)
    super
    add_after_hook_dependencies(dependencies)
    add_after_hook_dependencies(session)
    end

    private

    def add_before_hook_dependencies(dependencies)
    def add_before_hook_dependencies(session)
    # make before hooks dependent on the parent(s) before hooks
    parents.each do |parent|
    next unless parent.respond_to?(:before_hooks)
    @@ -143,10 +143,10 @@ def add_before_hook_dependencies(dependencies)
    parents.concat(before_hooks)

    # add before hooks to dependencies
    dependencies.concat(before_hooks)
    session.dependencies.concat(before_hooks)
    end

    def add_after_hook_dependencies(dependencies)
    def add_after_hook_dependencies(session)
    # make after hooks dependent on the parent(s) after hooks
    parents.each do |parent|
    next unless parent.respond_to?(:after_hooks)
    @@ -157,7 +157,7 @@ def add_after_hook_dependencies(dependencies)
    after_hooks.each { |hook| hook.parents << self }

    # add after hooks to dependencies
    dependencies.concat(after_hooks)
    session.dependencies.concat(after_hooks)
    end

    def command_name
    @@ -191,17 +191,17 @@ def call
    resource.send(:_create)
    end

    def add_to_dependencies(dependencies)
    def add_to_session(session)
    super

    # make setting the FK dependent on saving the parent, and
    # make the current command dependent on the FK being set
    resource.send(:parent_relationships).each do |relationship|
    parent = relationship.get!(resource)
    foreign_key = SetForeignKey.new(resource, relationship)
    foreign_key.parents << parent.save_command
    parents << foreign_key
    dependencies << foreign_key
    foreign_key.parents << parent.save_command
    parents << foreign_key
    session.dependencies << foreign_key
    end
    end
    end
  7. dkubb revised this gist Mar 20, 2010. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -142,7 +142,7 @@ def add_before_hook_dependencies(dependencies)
    # make current command dependent on before_hook
    parents.concat(before_hooks)

    # add before hooks to depdendencies
    # add before hooks to dependencies
    dependencies.concat(before_hooks)
    end

    @@ -156,7 +156,7 @@ def add_after_hook_dependencies(dependencies)
    # make after hooks dependent on curent command
    after_hooks.each { |hook| hook.parents << self }

    # add after hooks to depdendencies
    # add after hooks to dependencies
    dependencies.concat(after_hooks)
    end

  8. dkubb revised this gist Mar 20, 2010. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -5,6 +5,9 @@
    # order, and foreign keys set before children are saved, but after parents
    # are saved.

    # The hooks should fire in the following order:
    # https://gist.github.com/6666d2818b14296a28ab

    require 'tsort'

    require 'rubygems'
  9. dkubb revised this gist Mar 20, 2010. 1 changed file with 25 additions and 18 deletions.
    43 changes: 25 additions & 18 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -408,40 +408,47 @@ class Person

    __END__

    Getting close. The only thing I need to do is make sure before(:save)
    filters are fired before before(:create) and before(:update) filters, and
    than after(:save) filers are figred after after(:create) and after(:update)
    filters.

    OUTPUT:

    ~ (0.000150) SELECT sqlite_version(*)
    ~ (0.000189) DROP TABLE IF EXISTS "people"
    ~ (0.000039) PRAGMA table_info("people")
    ~ (0.000475) CREATE TABLE "people" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "name" VARCHAR(50) NOT NULL, "parent_id" INTEGER)
    ~ (0.000133) CREATE INDEX "index_people_parent" ON "people" ("parent_id")
    ~ (0.000117) CREATE UNIQUE INDEX "unique_people_name" ON "people" ("name")
    ~ (0.000168) SELECT sqlite_version(*)
    ~ (0.000186) DROP TABLE IF EXISTS "people"
    ~ (0.000030) PRAGMA table_info("people")
    ~ (0.000476) CREATE TABLE "people" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "name" VARCHAR(50) NOT NULL, "parent_id" INTEGER)
    ~ (0.000132) CREATE INDEX "index_people_parent" ON "people" ("parent_id")
    ~ (0.000127) CREATE UNIQUE INDEX "unique_people_name" ON "people" ("name")
    --------------------------------------------------------------------------------
    Before Saving Dan Kubb
    Before Creating Dan Kubb
    ~ (0.000109) INSERT INTO "people" ("name") VALUES ('Dan Kubb')
    ~ (0.000116) INSERT INTO "people" ("name") VALUES ('Dan Kubb')
    After Creating Dan Kubb
    After Saving Dan Kubb
    Before Saving Alex Kubb
    Before Creating Alex Kubb
    ~ (0.000066) INSERT INTO "people" ("name", "parent_id") VALUES ('Alex Kubb', 1)
    ~ (0.000069) INSERT INTO "people" ("name", "parent_id") VALUES ('Alex Kubb', 1)
    After Creating Alex Kubb
    After Saving Alex Kubb
    Before Saving Katie Kubb
    Before Creating Katie Kubb
    ~ (0.000059) INSERT INTO "people" ("name", "parent_id") VALUES ('Katie Kubb', 1)
    ~ (0.000062) INSERT INTO "people" ("name", "parent_id") VALUES ('Katie Kubb', 1)
    After Creating Katie Kubb
    After Saving Katie Kubb
    --------------------------------------------------------------------------------
    Before Saving Barbara-Ann Kubb
    Before Updating Barbara-Ann Kubb
    ~ (0.000133) UPDATE "people" SET "name" = 'Barbara-Ann Kubb' WHERE "id" = 1
    ~ (0.000127) UPDATE "people" SET "name" = 'Barbara-Ann Kubb' WHERE "id" = 1
    After Updating Barbara-Ann Kubb
    After Saving Barbara-Ann Kubb
    Before Saving Alexander Kubb
    Before Updating Alexander Kubb
    ~ (0.000071) UPDATE "people" SET "name" = 'Alexander Kubb' WHERE "id" = 2
    ~ (0.000073) UPDATE "people" SET "name" = 'Alexander Kubb' WHERE "id" = 2
    After Updating Alexander Kubb
    After Saving Alexander Kubb
    Before Saving Katherine Kubb
    Before Updating Katherine Kubb
    ~ (0.000069) UPDATE "people" SET "name" = 'Katherine Kubb' WHERE "id" = 3
    ~ (0.000087) UPDATE "people" SET "name" = 'Katherine Kubb' WHERE "id" = 3
    After Updating Katherine Kubb
    After Saving Katherine Kubb
    --------------------------------------------------------------------------------
    Before Destroying Barbara-Ann Kubb
    ~ (0.000070) DELETE FROM "people" WHERE "id" = 1
    ~ (0.000136) DELETE FROM "people" WHERE "id" = 1
    After Destroying Barbara-Ann Kubb
  10. dkubb revised this gist Mar 20, 2010. 1 changed file with 28 additions and 7 deletions.
    35 changes: 28 additions & 7 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -23,7 +23,6 @@ def self.scope

    # commit in the outer-most block
    session.commit if original.nil?

    rescue Exception
    session.rollback if original.nil?
    raise
    @@ -175,6 +174,13 @@ def self.new(resource)
    super
    end
    end

    def initialize(*)
    super

    before_hooks.unshift BeforeHook.new(resource, 'before_save_hook')
    after_hooks.push AfterHook.new(resource, 'after_save_hook')
    end
    end

    class Create < Save
    @@ -222,22 +228,37 @@ def initialize(resource, relationship)
    def call
    relationship.set(resource, relationship.get!(resource))
    end

    def ==(other)
    super && relationship == other.relationship
    end

    def eql?(other)
    super && relationship.eql?(other.relationship)
    end

    end

    class Hook < Command
    attr_reader :hook_name
    attr_reader :name

    def initialize(resource, hook_name)
    def initialize(resource, name)
    super(resource)
    @hook_name = hook_name
    @name = name
    end

    def call
    resource.send(hook_name)
    resource.send(name)
    true
    end

    private
    def ==(other)
    super && name == other.name
    end

    def eql?(other)
    super && name.eql?(other.name)
    end

    end

    @@ -329,7 +350,7 @@ def destroy
    end

    def save_command
    @save_command ||= Session::Save.new(self)
    Session::Save.new(self)
    end

    private
  11. dkubb revised this gist Mar 19, 2010. 1 changed file with 30 additions and 14 deletions.
    44 changes: 30 additions & 14 deletions test.rb
    Original file line number Diff line number Diff line change
    @@ -113,12 +113,13 @@ def add_to_dependencies(dependencies)
    end

    module HookableCommand
    def before_hook(name = "before_#{command_name}_hook")
    @before_hook ||= BeforeHook.new(resource, name)
    end
    attr_reader :before_hooks, :after_hooks

    def initialize(*)
    super

    def after_hook(name = "after_#{command_name}_hook")
    @after_hook ||= AfterHook.new(resource, name)
    @before_hooks = [ BeforeHook.new(resource, "before_#{command_name}_hook") ]
    @after_hooks = [ AfterHook.new(resource, "after_#{command_name}_hook") ]
    end

    def add_to_dependencies(dependencies)
    @@ -130,21 +131,31 @@ def add_to_dependencies(dependencies)
    private

    def add_before_hook_dependencies(dependencies)
    # make before hook dependent on the parent(s) before hook
    parents.each { |parent| before_hook.parents << parent.before_hook if parent.respond_to?(:before_hook) }
    # make before hooks dependent on the parent(s) before hooks
    parents.each do |parent|
    next unless parent.respond_to?(:before_hooks)
    before_hooks.each { |hook| hook.parents.concat(parent.before_hooks) }
    end

    # make current command dependent on before_hook
    parents << before_hook
    dependencies << before_hook
    parents.concat(before_hooks)

    # add before hooks to depdendencies
    dependencies.concat(before_hooks)
    end

    def add_after_hook_dependencies(dependencies)
    # make after hook dependent on the parent(s) after hook
    parents.each { |parent| after_hook.parents << parent.after_hook if parent.respond_to?(:after_hook) }
    # make after hooks dependent on the parent(s) after hooks
    parents.each do |parent|
    next unless parent.respond_to?(:after_hooks)
    after_hooks.each { |hook| hook.parents.concat(parent.after_hooks) }
    end

    # make after hooks dependent on curent command
    after_hooks.each { |hook| hook.parents << self }

    # make after hook dependent on curent command
    after_hook.parents << self
    dependencies << after_hook
    # add after hooks to depdendencies
    dependencies.concat(after_hooks)
    end

    def command_name
    @@ -254,6 +265,11 @@ def <<(command)
    self
    end

    def concat(commands)
    commands.each { |command| self << command }
    self
    end

    def valid?
    all? { |node| node.valid? }
    end
  12. dkubb renamed this gist Mar 19, 2010. 1 changed file with 38 additions and 43 deletions.
    81 changes: 38 additions & 43 deletions gistfile1.rb → test.rb
    Original file line number Diff line number Diff line change
    @@ -15,6 +15,22 @@ module DataMapper
    class Session
    attr_reader :dependencies

    def self.scope
    original = Thread.current[:dm_session]
    session = Thread.current[:dm_session] ||= new

    yield session

    # commit in the outer-most block
    session.commit if original.nil?

    rescue Exception
    session.rollback if original.nil?
    raise
    ensure
    Thread.current[:dm_session] = original
    end

    def initialize(&block)
    @dependencies = CommandDependencies.new
    end
    @@ -97,12 +113,12 @@ def add_to_dependencies(dependencies)
    end

    module HookableCommand
    def before_hook
    @before_hook ||= BeforeHook.new(self)
    def before_hook(name = "before_#{command_name}_hook")
    @before_hook ||= BeforeHook.new(resource, name)
    end

    def after_hook
    @after_hook ||= AfterHook.new(self)
    def after_hook(name = "after_#{command_name}_hook")
    @after_hook ||= AfterHook.new(resource, name)
    end

    def add_to_dependencies(dependencies)
    @@ -131,6 +147,10 @@ def add_after_hook_dependencies(dependencies)
    dependencies << after_hook
    end

    def command_name
    self.class.name.split('::').last.downcase
    end

    end

    class Save < Command
    @@ -194,11 +214,11 @@ def call
    end

    class Hook < Command
    attr_reader :command
    attr_reader :hook_name

    def initialize(command)
    super(command.resource)
    @command = command
    def initialize(resource, hook_name)
    super(resource)
    @hook_name = hook_name
    end

    def call
    @@ -208,22 +228,10 @@ def call

    private

    def command_name
    command.class.name.split('::').last.downcase
    end
    end

    class BeforeHook < Hook
    def hook_name
    "before_#{command_name}_hook"
    end
    end

    class AfterHook < Hook
    def hook_name
    "after_#{command_name}_hook"
    end
    end
    class BeforeHook < Hook; end
    class AfterHook < Hook; end

    class CommandDependencies
    include Enumerable, TSort
    @@ -284,12 +292,8 @@ def insertion_order
    end

    module Resource
    def save_command
    Session::Save.new(self)
    end

    def save
    session do |session|
    Session.scope do |session|
    # only allow a resource to be saved once
    return if session.include?(self)

    @@ -305,28 +309,19 @@ def save
    end

    def destroy
    session { |session| session.destroy(self) }
    Session.scope { |session| session.destroy(self) }
    end

    def child_resources
    child_collections.flatten
    def save_command
    @save_command ||= Session::Save.new(self)
    end

    def session
    original = Thread.current[:dm_session]
    session = Thread.current[:dm_session] ||= DataMapper::Session.new

    yield session

    # commit in the outer-most block
    session.commit if original.nil?
    private

    rescue Exception
    session.rollback if original.nil?
    raise
    ensure
    Thread.current[:dm_session] = original
    def child_resources
    child_collections.flatten
    end

    end
    end

  13. dkubb revised this gist Mar 19, 2010. 1 changed file with 180 additions and 26 deletions.
    206 changes: 180 additions & 26 deletions gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,10 @@
    # NOTE: this is a code spike, and the following code will probably
    # not make it into dm-core in it's current form. I wanted to see if it
    # were possible to use a topographical sort to ensure parents were
    # saved before children, before/after filters were fired in the correct
    # order, and foreign keys set before children are saved, but after parents
    # are saved.

    require 'tsort'

    require 'rubygems'
    @@ -18,9 +25,9 @@ def valid?

    def <<(resource)
    if command = resource.save_command
    dependencies << command
    command.add_to_dependencies(dependencies)
    else
    # TOOD: remove from the dependencies list
    # TODO: remove from the dependencies list
    # - would need to remove *all* references from all commands
    end
    self
    @@ -31,8 +38,12 @@ def concat(resources)
    self
    end

    def delete(resource)
    dependencies << Delete.new(resource)
    def include?(resource)
    dependencies.include?(resource.save_command)
    end

    def destroy(resource)
    Destroy.new(resource).add_to_dependencies(dependencies)
    self
    end

    @@ -47,6 +58,8 @@ def rollback
    freeze
    end

    private

    class Command
    attr_reader :resource, :parents

    @@ -74,12 +87,55 @@ def eql?(other)
    end

    def hash
    self.class.hash ^ resource.model.hash
    resource.object_id.hash
    end

    def add_to_dependencies(dependencies)
    dependencies << self
    end

    end

    module HookableCommand
    def before_hook
    @before_hook ||= BeforeHook.new(self)
    end

    def after_hook
    @after_hook ||= AfterHook.new(self)
    end

    def add_to_dependencies(dependencies)
    add_before_hook_dependencies(dependencies)
    super
    add_after_hook_dependencies(dependencies)
    end

    private

    def add_before_hook_dependencies(dependencies)
    # make before hook dependent on the parent(s) before hook
    parents.each { |parent| before_hook.parents << parent.before_hook if parent.respond_to?(:before_hook) }

    # make current command dependent on before_hook
    parents << before_hook
    dependencies << before_hook
    end

    def add_after_hook_dependencies(dependencies)
    # make after hook dependent on the parent(s) after hook
    parents.each { |parent| after_hook.parents << parent.after_hook if parent.respond_to?(:after_hook) }

    # make after hook dependent on curent command
    after_hook.parents << self
    dependencies << after_hook
    end

    end

    class Save < Command
    include HookableCommand

    def self.new(resource)
    if equal?(Save)
    klass = resource.new? ? Create : Update
    @@ -91,7 +147,11 @@ def self.new(resource)
    end

    class Create < Save
    def initialize(resource)
    def call
    resource.send(:_create)
    end

    def add_to_dependencies(dependencies)
    super

    # make setting the FK dependent on saving the parent, and
    @@ -101,12 +161,9 @@ def initialize(resource)
    foreign_key = SetForeignKey.new(resource, relationship)
    foreign_key.parents << parent.save_command
    parents << foreign_key
    dependencies << foreign_key
    end
    end

    def call
    resource.send(:_create)
    end
    end

    class Update < Save
    @@ -116,6 +173,8 @@ def call
    end

    class Destroy < Command
    include HookableCommand

    def call
    resource.send(:_destroy, false)
    end
    @@ -134,13 +193,45 @@ def call
    end
    end

    class Hook < Command
    attr_reader :command

    def initialize(command)
    super(command.resource)
    @command = command
    end

    def call
    resource.send(hook_name)
    true
    end

    private

    def command_name
    command.class.name.split('::').last.downcase
    end
    end

    class BeforeHook < Hook
    def hook_name
    "before_#{command_name}_hook"
    end
    end

    class AfterHook < Hook
    def hook_name
    "after_#{command_name}_hook"
    end
    end

    class CommandDependencies
    include Enumerable, TSort

    def initialize
    @commands = []
    @index_for = Hash.new do |hash, command|
    hash[command] = commands.index(command) || -1
    hash[command] = commands.index(command)
    end
    end

    @@ -198,23 +289,34 @@ def save_command
    end

    def save
    original = Thread.current[:dm_session]
    session = Thread.current[:dm_session] ||= DataMapper::Session.new
    session do |session|
    # only allow a resource to be saved once
    return if session.include?(self)

    # add parents to the UoW
    parent_resources.each { |parent| parent.save }

    # only allow a resource to be saved once
    return if session.dependencies.include?(save_command)
    # add resource to the UoW
    session << self

    # add parents to the UoW
    parent_resources.each { |parent| parent.save }
    # add children to the UoW
    child_resources.each { |child| child.save }
    end
    end

    # TODO: add before hook to be dependent on the parent(s) before hook
    def destroy
    session { |session| session.destroy(self) }
    end

    session << self
    def child_resources
    child_collections.flatten
    end

    # TODO: add after hook to be dependent on the parent(s) after hook
    def session
    original = Thread.current[:dm_session]
    session = Thread.current[:dm_session] ||= DataMapper::Session.new

    # add children to the UoW
    child_resources.each { |child| child.save }
    yield session

    # commit in the outer-most block
    session.commit if original.nil?
    @@ -225,10 +327,6 @@ def save
    ensure
    Thread.current[:dm_session] = original
    end

    def child_resources
    child_collections.flatten
    end
    end
    end

    @@ -243,6 +341,18 @@ class Person

    belongs_to :parent, self, :required => false
    has n, :children, self, :inverse => :parent

    before(:save) { puts "Before Saving #{name}" }
    after(:save) { puts "After Saving #{name}" }

    before(:create) { puts "Before Creating #{name}" }
    after(:create) { puts "After Creating #{name}" }

    before(:update) { puts "Before Updating #{name}" }
    after(:update) { puts "After Updating #{name}" }

    before(:destroy) { puts "Before Destroying #{name}" }
    after(:destroy) { puts "After Destroying #{name}" }
    end

    DataMapper.auto_migrate!
    @@ -259,3 +369,47 @@ class Person
    parent.children(:name => 'Alex Kubb').each { |child| child.name = 'Alexander Kubb' }
    parent.children(:name => 'Katie Kubb').each { |child| child.name = 'Katherine Kubb' }
    parent.save

    puts '-' * 80
    #parent.children.destroy
    parent.destroy

    __END__

    Getting close. The only thing I need to do is make sure before(:save)
    filters are fired before before(:create) and before(:update) filters, and
    than after(:save) filers are figred after after(:create) and after(:update)
    filters.

    OUTPUT:

    ~ (0.000150) SELECT sqlite_version(*)
    ~ (0.000189) DROP TABLE IF EXISTS "people"
    ~ (0.000039) PRAGMA table_info("people")
    ~ (0.000475) CREATE TABLE "people" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "name" VARCHAR(50) NOT NULL, "parent_id" INTEGER)
    ~ (0.000133) CREATE INDEX "index_people_parent" ON "people" ("parent_id")
    ~ (0.000117) CREATE UNIQUE INDEX "unique_people_name" ON "people" ("name")
    --------------------------------------------------------------------------------
    Before Creating Dan Kubb
    ~ (0.000109) INSERT INTO "people" ("name") VALUES ('Dan Kubb')
    After Creating Dan Kubb
    Before Creating Alex Kubb
    ~ (0.000066) INSERT INTO "people" ("name", "parent_id") VALUES ('Alex Kubb', 1)
    After Creating Alex Kubb
    Before Creating Katie Kubb
    ~ (0.000059) INSERT INTO "people" ("name", "parent_id") VALUES ('Katie Kubb', 1)
    After Creating Katie Kubb
    --------------------------------------------------------------------------------
    Before Updating Barbara-Ann Kubb
    ~ (0.000133) UPDATE "people" SET "name" = 'Barbara-Ann Kubb' WHERE "id" = 1
    After Updating Barbara-Ann Kubb
    Before Updating Alexander Kubb
    ~ (0.000071) UPDATE "people" SET "name" = 'Alexander Kubb' WHERE "id" = 2
    After Updating Alexander Kubb
    Before Updating Katherine Kubb
    ~ (0.000069) UPDATE "people" SET "name" = 'Katherine Kubb' WHERE "id" = 3
    After Updating Katherine Kubb
    --------------------------------------------------------------------------------
    Before Destroying Barbara-Ann Kubb
    ~ (0.000070) DELETE FROM "people" WHERE "id" = 1
    After Destroying Barbara-Ann Kubb
  14. dkubb revised this gist Mar 18, 2010. 1 changed file with 25 additions and 6 deletions.
    31 changes: 25 additions & 6 deletions gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -91,12 +91,20 @@ def self.new(resource)
    end

    class Create < Save
    def call
    # XXX: move this to an explicit hook
    def initialize(resource)
    super

    # make setting the FK dependent on saving the parent, and
    # make the current command dependent on the FK being set
    resource.send(:parent_relationships).each do |relationship|
    relationship.set(resource, relationship.get!(resource))
    parent = relationship.get!(resource)
    foreign_key = SetForeignKey.new(resource, relationship)
    foreign_key.parents << parent.save_command
    parents << foreign_key
    end
    end

    def call
    resource.send(:_create)
    end
    end
    @@ -113,13 +121,26 @@ def call
    end
    end

    class SetForeignKey < Command
    attr_reader :relationship

    def initialize(resource, relationship)
    super(resource)
    @relationship = relationship
    end

    def call
    relationship.set(resource, relationship.get!(resource))
    end
    end

    class CommandDependencies
    include Enumerable, TSort

    def initialize
    @commands = []
    @index_for = Hash.new do |hash, command|
    hash[command] = commands.index(command)
    hash[command] = commands.index(command) || -1
    end
    end

    @@ -187,8 +208,6 @@ def save
    parent_resources.each { |parent| parent.save }

    # TODO: add before hook to be dependent on the parent(s) before hook
    # TODO: add command to set the FK, dependent on the parent(s) save
    # TODO: set save command to be dependent on the FK setting command

    session << self

  15. dkubb revised this gist Mar 18, 2010. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -165,7 +165,7 @@ def tsort_each_child(node, &block)
    end

    def insertion_order
    lambda { |command| index_for[command] }
    lambda { |command| index_for[command] || raise("XXX: DEBUG: unknown command #{command.inspect}") }
    end

    end
  16. dkubb revised this gist Mar 18, 2010. 1 changed file with 68 additions and 54 deletions.
    122 changes: 68 additions & 54 deletions gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -17,9 +17,11 @@ def valid?
    end

    def <<(resource)
    if resource.new? then register_new(resource)
    elsif resource.dirty? then register_dirty(resource)
    else register_clean(resource)
    if command = resource.save_command
    dependencies << command
    else
    # TOOD: remove from the dependencies list
    # - would need to remove *all* references from all commands
    end
    self
    end
    @@ -30,7 +32,7 @@ def concat(resources)
    end

    def delete(resource)
    register_deleted(resource)
    dependencies << Delete.new(resource)
    self
    end

    @@ -45,62 +47,69 @@ def rollback
    freeze
    end

    private
    class Command
    attr_reader :resource, :parents

    def register_new(resource)
    dependencies << Persistence::Create.new(resource)
    end
    def initialize(resource)
    @resource = resource
    @parents = resource.send(:parent_resources).map do |parent|
    parent.save_command
    end
    end

    def register_dirty(resource)
    dependencies << Persistence::Update.new(resource)
    end
    def valid?
    resource.valid?
    end

    def register_clean(resource)
    # TOOD: remove from the dependencies list
    # - would need to remove *all* references from all commands
    end
    def call
    raise NotImplementedError, "#{self.class}#call not implemented"
    end

    def register_deleted(resource)
    dependencies << Persistence::Delete.new(resource)
    end
    def ==(other)
    kind_of?(other.class) && resource == other.resource
    end

    module Persistence
    class Command
    attr_reader :resource
    def eql?(other)
    instance_of?(other.class) && resource.eql?(other.resource)
    end

    def initialize(resource)
    @resource = resource
    end
    def hash
    self.class.hash ^ resource.model.hash
    end

    def valid?
    resource.valid?
    end
    end

    def call
    raise NotImplementedError, "#{self.class}#call not implemented"
    class Save < Command
    def self.new(resource)
    if equal?(Save)
    klass = resource.new? ? Create : Update
    klass.new(resource)
    else
    super
    end

    end
    end

    class Create < Command
    def call
    puts "XXX: DEBUG: #{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_create)
    class Create < Save
    def call
    # XXX: move this to an explicit hook
    resource.send(:parent_relationships).each do |relationship|
    relationship.set(resource, relationship.get!(resource))
    end

    resource.send(:_create)
    end
    end

    class Update < Command
    def call
    puts "XXX: DEBUG: #{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_update)
    end
    class Update < Save
    def call
    resource.send(:_update)
    end
    end

    class Destroy < Command
    def call
    puts "XXX: DEBUG: #{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_destroy, false)
    end
    class Destroy < Command
    def call
    resource.send(:_destroy, false)
    end
    end

    @@ -156,20 +165,23 @@ def tsort_each_child(node, &block)
    end

    def insertion_order
    lambda { |command| index_for[command] || raise("command #{command.inspect} was not found") }
    lambda { |command| index_for[command] }
    end

    end

    end

    module Resource
    def save_command
    Session::Save.new(self)
    end

    def save
    original = Thread.current[:dm_session]
    session = Thread.current[:dm_session] ||= DataMapper::Session.new

    # only allow a resource to be seen once
    return if session.dependencies.any? { |command| command.resource == self }
    # only allow a resource to be saved once
    return if session.dependencies.include?(save_command)

    # add parents to the UoW
    parent_resources.each { |parent| parent.save }
    @@ -187,8 +199,9 @@ def save

    # commit in the outer-most block
    session.commit if original.nil?

    rescue Exception
    rollback
    session.rollback if original.nil?
    raise
    ensure
    Thread.current[:dm_session] = original
    @@ -211,10 +224,6 @@ class Person

    belongs_to :parent, self, :required => false
    has n, :children, self, :inverse => :parent

    def inspect
    name
    end
    end

    DataMapper.auto_migrate!
    @@ -224,5 +233,10 @@ def inspect
    parent.children.new(:name => 'Katie Kubb')

    puts '-' * 80
    parent.save

    parent.save
    puts '-' * 80
    parent.attributes = { :name => 'Barbara-Ann Kubb' }
    parent.children(:name => 'Alex Kubb').each { |child| child.name = 'Alexander Kubb' }
    parent.children(:name => 'Katie Kubb').each { |child| child.name = 'Katherine Kubb' }
    parent.save
  17. dkubb revised this gist Mar 17, 2010. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -84,21 +84,21 @@ def call

    class Create < Command
    def call
    puts "#{self.class.name.split('::').last} #{resource.name}"
    puts "XXX: DEBUG: #{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_create)
    end
    end

    class Update < Command
    def call
    puts "#{self.class.name.split('::').last} #{resource.name}"
    puts "XXX: DEBUG: #{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_update)
    end
    end

    class Destroy < Command
    def call
    puts "#{self.class.name.split('::').last} #{resource.name}"
    puts "XXX: DEBUG: #{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_destroy, false)
    end
    end
  18. dkubb revised this gist Mar 17, 2010. 1 changed file with 0 additions and 12 deletions.
    12 changes: 0 additions & 12 deletions gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -72,18 +72,6 @@ def initialize(resource)
    @resource = resource
    end

    def new?
    resource.new?
    end

    def saved?
    resource.saved?
    end

    def dirty?
    resource.send(:dirty_self?)
    end

    def valid?
    resource.valid?
    end
  19. dkubb revised this gist Mar 17, 2010. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -186,13 +186,13 @@ def save
    # add parents to the UoW
    parent_resources.each { |parent| parent.save }

    # TODO: add before hook to be dependent on the parent before hook
    # TODO: add command to set the FK, dependent on the parent save
    # TODO: add before hook to be dependent on the parent(s) before hook
    # TODO: add command to set the FK, dependent on the parent(s) save
    # TODO: set save command to be dependent on the FK setting command

    session << self

    # TODO: add after hook to be dependent on the parent after hook
    # TODO: add after hook to be dependent on the parent(s) after hook

    # add children to the UoW
    child_resources.each { |child| child.save }
  20. dkubb created this gist Mar 17, 2010.
    240 changes: 240 additions & 0 deletions gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,240 @@
    require 'tsort'

    require 'rubygems'
    require 'dm-core'
    require 'dm-validations'

    module DataMapper
    class Session
    attr_reader :dependencies

    def initialize(&block)
    @dependencies = CommandDependencies.new
    end

    def valid?
    dependencies.valid?
    end

    def <<(resource)
    if resource.new? then register_new(resource)
    elsif resource.dirty? then register_dirty(resource)
    else register_clean(resource)
    end
    self
    end

    def concat(resources)
    resources.each { |resource| self << resource }
    self
    end

    def delete(resource)
    register_deleted(resource)
    self
    end

    def commit
    dependencies.call
    freeze
    end

    def rollback
    # TODO: undo changes made in this session
    dependencies.clear
    freeze
    end

    private

    def register_new(resource)
    dependencies << Persistence::Create.new(resource)
    end

    def register_dirty(resource)
    dependencies << Persistence::Update.new(resource)
    end

    def register_clean(resource)
    # TOOD: remove from the dependencies list
    # - would need to remove *all* references from all commands
    end

    def register_deleted(resource)
    dependencies << Persistence::Delete.new(resource)
    end

    module Persistence
    class Command
    attr_reader :resource

    def initialize(resource)
    @resource = resource
    end

    def new?
    resource.new?
    end

    def saved?
    resource.saved?
    end

    def dirty?
    resource.send(:dirty_self?)
    end

    def valid?
    resource.valid?
    end

    def call
    raise NotImplementedError, "#{self.class}#call not implemented"
    end

    end

    class Create < Command
    def call
    puts "#{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_create)
    end
    end

    class Update < Command
    def call
    puts "#{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_update)
    end
    end

    class Destroy < Command
    def call
    puts "#{self.class.name.split('::').last} #{resource.name}"
    resource.send(:_destroy, false)
    end
    end
    end

    class CommandDependencies
    include Enumerable, TSort

    def initialize
    @commands = []
    @index_for = Hash.new do |hash, command|
    hash[command] = commands.index(command)
    end
    end

    def clear
    @commands.clear
    @index_for.clear
    self
    end

    def <<(command)
    commands << command
    self
    end

    def valid?
    all? { |node| node.valid? }
    end

    def each
    tsort_each { |node| yield node }
    self
    end

    def call
    all? { |node| node.call }
    end

    private

    attr_reader :commands, :index_for

    def tsort_each_node(&block)
    commands.sort_by(&insertion_order).each(&block)
    end

    def tsort_each_child(node, &block)
    # tsort places child nodes before parent nodes, yet this makes
    # no sense from a UoW pov. Parents should always be saved first.
    # I have no idea why this method in tsort is named this way.
    if commands.include?(node) && node.respond_to?(:parents)
    node.parents.sort_by(&insertion_order).each(&block)
    end
    end

    def insertion_order
    lambda { |command| index_for[command] || raise("command #{command.inspect} was not found") }
    end

    end

    end

    module Resource
    def save
    original = Thread.current[:dm_session]
    session = Thread.current[:dm_session] ||= DataMapper::Session.new

    # only allow a resource to be seen once
    return if session.dependencies.any? { |command| command.resource == self }

    # add parents to the UoW
    parent_resources.each { |parent| parent.save }

    # TODO: add before hook to be dependent on the parent before hook
    # TODO: add command to set the FK, dependent on the parent save
    # TODO: set save command to be dependent on the FK setting command

    session << self

    # TODO: add after hook to be dependent on the parent after hook

    # add children to the UoW
    child_resources.each { |child| child.save }

    # commit in the outer-most block
    session.commit if original.nil?
    rescue Exception
    rollback
    raise
    ensure
    Thread.current[:dm_session] = original
    end

    def child_resources
    child_collections.flatten
    end
    end
    end

    DataMapper::Logger.new($stdout, :debug)
    DataMapper.setup(:default, 'sqlite3::memory:')

    class Person
    include DataMapper::Resource

    property :id, Serial
    property :name, String, :length => 1..50, :required => true, :unique => true, :unique_index => true

    belongs_to :parent, self, :required => false
    has n, :children, self, :inverse => :parent

    def inspect
    name
    end
    end

    DataMapper.auto_migrate!

    parent = Person.new(:name => 'Dan Kubb')
    parent.children.new(:name => 'Alex Kubb')
    parent.children.new(:name => 'Katie Kubb')

    puts '-' * 80

    parent.save