Ich bin mit dir verheiratet
⇔ Du bist mit mir verheiratet
class Movie < ActiveRecord::Base has_and_belongs_to_many :actors, :class_name => 'Person' # ... für Regie, Kamera, Schnitt, Makeup, ... endNein, so nicht.
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 => ...)Das tut ja weh.
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
JA!
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
Muss das sein?
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 ist der Abstraktion zum Opfer gefallen.
{
"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 ich.marriages.create(:spouse => du) du.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
ich.marriages.create(:spouse => du)
ich.spouses.include?(du) # => true
du.spouses.include?(ich) # => true
du.marriages.create(:spouse => no3)
...
has_many :spouses, :through => :marriages nicht möglich
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,
aber ActiveRecord merkt davon nichts.
validates_presence_of: ja, das ist (inzwischen) richtig so.
Früher mußte der Foreign Key angegeben werden, heute ist (auch?) die
Assoziation selbst korrekt.
CREATE RULE
SELECT
SELECT, INSERT, UPDATE, DELETE:config.active_record.schema_format = :sql
execute in der Migration ausführen.
UNION in Updatable Views umgehen.
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: Sicht auf die eingefügte Zeile;
ActiveRecord nimmt davon nur id.
lock_version fehlt.
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?.
Die marriage-Assoziation wird früher oder
später ohnehin geladen.
| Prozess1 | Prozess2 |
|---|---|
|
|
|
|
validate | |
validate | |
save | |
save |
class Marriage < ActiveRecord::Base
def before_validation
...
Person.find(:all,
:conditions => { :id => [person, spouse].compact },
:lock => true)
end
end
Nicht:
def before_validation
...
person.try(:lock!)
spouse.try(:lock!) # There be deadlocks
end
#compact, weil person/spouse
nil sein können.
#lock! genügen nicht: Gefahr eines Deadlocks.
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
War das jetzt
has_many, ...named_scope, ...CREATE VIEWCREATE RULEMehr über mich und von mir:
http://www.schuerig.de/michael
Zum Nachschauen:
http://www.schuerig.de/michael/pres/kreative-assoziationen/