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 endMuss 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 endhttp://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
id
s,
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 endNicht:
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 VIEW
CREATE RULE
Mehr über mich und von mir:
http://www.schuerig.de/michael
Zum Nachschauen:
http://www.schuerig.de/michael/pres/kreative-assoziationen/