# i'm working on a data model that requires many, many join tables.  this is a
# pain because of all migrations i need to run and the thinking/re-thinking
# that gets done along the way - not to mention setting up all the assocations
# in AR by hand for each relationship.  i looked into using something like
# has_many_polymorphs(http://github.com/fauna/has_many_polymorphs/tree/master)
# which is a fantastic plugin i've used before with good success.  however
# it's a bit too heavy-weight for my use this time - besides all i really want
# to do is avoid having a massive proliferation of join tables and crazy-ass
# polymorphic fields that make my sql life hell.  following is an *extremely*
# simple solution i came up with to solve the 'having many "has_many"
# relationships and way too many join tables issue.
#
# the basic concept is to have one mother of a join table through which to
# join all related models.  the key here, and this is differnt than HMP, is
# that our *join* model will use sti, not polymorphic associations and, imho,
# this is vastly simpler and more flexible.  first, we setup our join table and
# it's migration:
#
  class Join < ActiveRecord::Base

  # here's an example migration for the mother join table
  #
  # cfp:~> cat db/migrate/20081024060458_create_joins.rb
  #   CreateJoins = Join::Migration 
  #
  # or just run
  #
  #   ./script/runner ' Join::Migration.up '
  #
    class Migration < ActiveRecord::Migration
      def self.up
        create_table :joins do |t|
          t.string :type  # sti!!!!!!!!!!!!!!!!!!!!!!!!!!!!
          t.integer :src
          t.integer :dst
          t.timestamps
        end
      end

      def self.down
        drop_table :joins
      end
    end
  end

# now assume we want to associate a Person with multiple parents and multiple
# children - normally this would require two join models, but we'll instead
# use two STI based join models which can be created ad-hoc, without needing
# additional tables or even additional keys on the source models
#
  class Join
  # maps a person to their children
  #
    class PersonChild < Join
      belongs_to :person, :foreign_key => :src
      belongs_to :child, :foreign_key => :dst
    end

  # maps a person to their parents 
  #
    class PersonParent < Join
      belongs_to :person, :foreign_key => :src
      belongs_to :parent, :foreign_key => :dst
    end
  end

# now all we have to do is setup the 'normal' has_many through associations,
# has_and_belongs_to_many, whatever...
#
  class Person < ActiveRecord::Base
    has_many :child_joins, :foreign_key => :src, :class_name => 'Join::PersonChild'
    has_many :children, :through => :child_joins

    has_many :parent_joins, :foreign_key => :src, :class_name => 'Join::PersonParent'
    has_many :parents, :through => :parent_joins
  end
  class Child; end
  class Parent; end

# vie-oh-la!  we can do stuff like this:
#
#   cfp:~/rails_root > ./script/console
#
#   Loading development environment (Rails 2.1.1)
#
#   >> p=Person.create
#   => #<Person id: 6>
#
#   >> p.parents
#   => []
#
#   >> p.parents << Parent.create
#   => [#<Parent id: 42>]
#
#   >> p.parent_joins
#   => [#<Join::PersonParent id: 2, type: "Join::PersonParent", src: 6, dst: 42>]
#


# now, adding more relationships is as simple as
#
  class Person < ActiveRecord::Base
  # define the join - note that we can do this from anywhere really although
  # i'd probably keep them all defined in the join model...
  #
    class Join::PersonDog < ::Join
      belongs_to :person, :foreign_key => :src
      belongs_to :dog, :foreign_key => :dst
    end

  # and setup relationships through the sti join model
  #
    has_many :dog_joins, :foreign_key => :src, :class_name => 'Join::PersonDog'
    has_many :dogs, :through => :dog_joins
  end




# UPDATE: i've added a simple class generator so you don't have to even think
# about building the join models, it looks like this
#
  class Join
    def Join.for *args
      desc = args.shift
      src, dst, *ignored = desc.to_a.flatten

      src_options = (args.shift || {}).to_options
      dst_options = (args.shift || {}).to_options

      const = "#{ src }_#{ dst }".camelize

      unless const_defined?(const)
        join_model = Class.new(Join)
        const_set const, join_model
        join_model.module_eval do
          belongs_to src.to_s.to_sym, src_options.reverse_merge(:foreign_key => :src)
          belongs_to dst.to_s.to_sym, dst_options.reverse_merge(:foreign_key => :dst)
        end
      end

      "::Join::#{ const }"
    end
  end

# now you can get the name for a join class and create it all in *one step*,
# like so.  of course the manual way is still supported.
#

  class Person < ActiveRecord::Base
    has_many :parent_joins, :foreign_key => :src, :class_name => Join.for(:person => :parent)
    has_many :parents, :through => :parent_joins
  end

# you can grab the code here - just put it in ./app/models/
#
# http://s3.amazonaws.com/drawohara.com.ruby/join.rb
#