I am married to you 
      ⇔ You are married to me
    
class Movie < ActiveRecord::Base has_and_belongs_to_many :actors, :class_name => 'Person' # ... same for director, camera, cutter, makeup, ... endThat's not the way..
class Movie < ActiveRecord::Base
  has_many :roles
  has_many :participants, :through => :roles,
    :source => :person
end
class Role < ActiveRecord::Base
  validates_presence_of :role_type
  belongs_to :person
  belongs_to :movie
end
    movie.participants.find(:all, :conditions => ...)That hurts!
class Movie < ActiveRecord::Base
  has_many :roles do
    def as_actor
      self.scoped(
        :joins => 'CROSS JOIN roles',
        :conditions => { :roles => { :role_type => "actor" } }
      )
    end
    def as_director
      ...
    end
  end
  has_many :participants, :through => :roles,
      :source => :person do
    ...
  end
end
    YES!
class Movie < ActiveRecord::Base
  has_many :roles do
    def as(role_type)
      self.scoped(
        :joins => 'CROSS JOIN roles',
        :conditions => { :roles => { :role_type => role_type }}
      )
    end
  end
end
    
class Movie < ActiveRecord::Base
  has_many :roles do
    def as(role_type)
      ...
    end
    [:actor, :director].each do |role_type|
      define_method("as_#{role_type}") do
        as(role_type)
      end
    end
  end
end
    
class Movie < ActiveRecord::Base
  has_many :roles do
    ...
  end
  has_many :participants, :through => :roles,
      :source => :person do
    def as(role_type)
      ...
    end
    [:actor, :director].each do |role_type|
      define_method ...
    end
  end
end
    Is this necessary?
    
class Movie < ActiveRecord::Base
  has_many :roles, :extend => RoleTypeExtension
  has_many :participants, :through => :roles,
    :source => :person,
    :extend => RoleTypeExtension
end
module RoleTypeExtension
  def as(role_type)
    ...
  end
  [:actor, :director].each do |role_type|
    define_method ...
  end
end
    
class Person < ActiveRecord::Base
  has_many :roles, :extend => RoleTypeExtension
  has_many :movies, :through => :roles,
    :extend => RoleTypeExtension
end
    
class Person < ActiveRecord::Base
  named_scope :actors, {
    :joins =>
      'INNER JOIN roles ON roles.person_id = people.id',
    :conditions => { :roles => { :role_type => 'actor' }
  }
end
    
class Person < ActiveRecord::Base
  ['actor', 'director'] do |role_type|
    named_scope role_type.pluralize,
      {
        :joins =>
          'INNER JOIN roles ON roles.person_id = people.id',
        :conditions => { :roles => { :role_type => role_type }
      }
    end
  end
end
    ['actor', 'director'] do |role_type|
    
class Role < ActiveRecord::Base
  ROLE_TYPES = %w(actor director).freeze
  def self.each_role_type(&block)
    ROLE_TYPES.each(&block)
  end
end
class Person < ActiveRecord::Base
  Role.each_role_type do |role_type|
    named_scope role_type.pluralize,
      ...
    movie.participants.add(... ? ...) movie.participants.remove(... ? ...)
class Movie < ActiveRecord::Base
  has_many :roles, :extend => RoleTypeExtension
  has_many :participants, :through => :roles,
    :source => :person, :extend => ParticipantsExtension
  module ParticipantsExtension
    include RoleTypeExtension
    ...
  end
end
module RoleTypeExtension
  ...
end
    
module ParticipantsExtension
  include RoleTypeExtension
  def add(role_type, person)
    proxy_owner.roles.build(
      :person    => person,
      :role_type => role_type)
  end
  def remove(role_type, person)
    role = Role.find(:first,
      :joins => :roles,
      :conditions => {
        :person_id => person,
        :movie_id  => proxy_owner,
        :role_type => role_type })
    proxy_owner.roles.delete(role)
  end
end
    
class MoviesController < ApplicationController
  def create
    @movie = Movie.new(params[:movie])
    respond_to do |format|
      ...
    end
  end
  def update
    @movie = Movie.find(params[:id])
    @movie.update_attributes(params[:movie])
    respond_to do |format|
      ...
    end
  end
end
    
{
  "id" => 4217,
  "movie" => {
    "title"        => "Tramping Along the Rails",
    "actor_ids"    => ["1", "2", "3", "5", "8"],
    "director_ids" => ["13"]
  }
}
    
{
  "id" => 4217,
  "movie" => {
    "title"     => "Tramping Along the Rails",
    "actors"    => [
      { "person_id" => "1", "credited_as" => "Will Shatter", 
        "character" => "Captain Krak" }
      { "person_id" => "7", "credited_as" => "Lemur Nerode",
        "character" => "Mr Conehead" }
    ],
    ...
  }
}
    Movie#actors was abstracted away
      
{
  "id" => 4217,
  "movie" => {
    "title"            => "Tramping Along the Rails",
    "roles_attributes" => [
      { "id" => "1", "credited_as" => "Will Shatter", 
        "character" => "Captain Krak", "role_type" => "actor" }
      { "id" => "13", "_delete" => "1" }
    ],
    ...
  }
}
    class Movie < ActiveRecord::Base has_many :roles, :extend => RoleTypeExtension accepts_nested_attributes_for :roles, :allow_destroy => true end
fields_for
        class Person < ActiveRecord::Base has_many :marriages has_many :spouses, :through => :marriages, :source => :spouse end class Marriage < ActiveRecord::Base belongs_to :person belongs_to :spouse, :class_name => 'Person' end me.marriages.create(:spouse => you) you.spouses == ?
class Person < ActiveRecord::Base
  has_and_belongs_to_many :marriages
  def spouses
    marriages.map(&:people).flatten - [self]
  end
end
class Marriage < ActiveRecord::Base
  has_and_belongs_to_many :people
end
me.marriages.create(:spouse => you)
me.spouses.include?(you) # => true
you.spouses.include?(you) # => true
you.marriages.create(:spouse => no3)
...
    has_many :spouses, :through => :marriages is not possible
        
class Person < ActiveRecord::Base
  has_many :marriages
  has_many :spouses, :through => :marriages, :source => :spouse
end
class Marriage < ActiveRecord::Base
  validates_presence_of :start_date
  belongs_to :person
  belongs_to :spouse, :class_name => 'Person'
  after_create  { |m| Marriage.create(:person => m.spouse,
                                      :spouse => m.person) }
  after_destroy { |m| Marriage.delete_all(:conditions => ...) }
end
    
create_table :marriages_internal do |t|
  t.belongs_to :person1, :null => false
  t.belongs_to :person2, :null => false
end
add_index :marriages_internal, [:person1_id, :person2_id],
  :unique => true
create_view :marriages,
  %{SELECT id, person1_id, person2_id FROM marriages_internal
    UNION
    SELECT id, person2_id, person1_id FROM marriages_internal
  } do |v|
  v.column :id
  v.column :person_id
  v.column :spouse_id
end
    http://github.com/aeden/rails_sql_views
    start_date, end_date,
          lock_version.
        class Marriage < ActiveRecord::Base belongs_to :person belongs_to :spouse, :class_name => 'Person' validates_presence_of :person, :spouse end class Person < ActiveRecord::Base has_one :marriage, :conditions => 'end_date IS NULL' has_one :spouse, :through => :marriage end
ids,
          but ActiveRecord doesn't care.
        validates_presence_of: yes, that's become correct in the meantime.
        CREATE RULE
    SELECT
      SELECT, INSERT, UPDATE, DELETE:config.active_record.schema_format = :sql
      execute in a migration.
        UNION in updatable views.
        
CREATE RULE marriages_ins AS ON INSERT TO marriages DO INSTEAD
INSERT INTO marriages_internal (person1_id, person2_id,
                                start_date, end_date)
VALUES (LEAST(NEW.person_id, NEW.spouse_id), 
        GREATEST(NEW.person_id, NEW.spouse_id))
RETURNING id, person1_id, person2_id, start_date, end_date;
    person1_id < person2_id.
        RETURNING: view on the inserted row
          ActiveRecord only takes id.
        lock_version not shown.
        
CREATE RULE marriages_upd AS ON UPDATE TO marriages DO INSTEAD
UPDATE marriages_internal
SET start_date   = NEW.start_date,
    end_date     = NEW.end_date
WHERE (id = OLD.id);
    CREATE RULE marriages_del AS ON DELETE TO marriages DO INSTEAD DELETE FROM marriages_internal WHERE (id = OLD.id);
class Marriage < ActiveRecord::Base
  def before_validation
    self.start_date ||= Date.today
  end
  def validate_on_create
    errors.add_to_base("...") if person == spouse
  end
  def validate
    if end_date && end_date < start_date
      errors.add(:end_date, "...")
    end
    validate_unmarried(person, :person_id)
    validate_unmarried(spouse, :spouse_id)
  end
  ...
    
  ...
  def period
    today = Date.today
    ((start_date || today)..(end_date || today))
  end
  def overlaps?(other_period)
    period.overlaps?(other_period)
  end
  def validate_unmarried(person, attribute)
    others = person.marriages.during(period) - [self]
    unless others.empty?
      errors.add(attribute, "Is already married at that time.")
    end
  end
end
    
class Person
  has_many :marriages do
    def during(dates)
      self.select { |marriage| marriage.overlaps?(dates) }
    end
  end
end
    #exists?.
          The marriage association will be loaded completely
          sooner or later anyway.
        | Process1 | Process2 | 
|---|---|
 | 
        |
 | 
      |
validate | |
validate | |
save | |
save | 
class Marriage < ActiveRecord::Base
  def before_validation
    ...
    Person.find(:all, 
      :conditions => { :id => [person, spouse].compact },
      :lock => true)
  end
end
    
    Not:
    
  def before_validation
    ...
    person.try(:lock!)
    spouse.try(:lock!) # There be deadlocks
  end
    #compact, because person/spouse
          may be nil.
        #lock! risk deadlock.
        ActiveRecord#lock!
        class Marriage < ActiveRecord::Base belongs_to :person, :touch => true belongs_to :spouse, :class_name => 'Person', :touch => true end
ActiveRecord::StaleObjectError: Attempted to update a stale object
      date = movie.release_date movie.participants.select do |brad| jennifer = brad.marriages.ended_before(date).last.spouse angelina = brad.marriages.started_after(date).first.spouse jennifer && angelina && movie.participants.include?(angelina) end
Have we been doing
has_many, ...named_scope, ...CREATE VIEWCREATE RULEMore about and by me:
http://www.schuerig.de/michael
      This presentation:
      http://www.schuerig.de/michael/pres/kreative-assoziationen/