I''m having trouble with this model: class Person < AR::B belongs_to :shipping_address, :class_name => ''Address'' belongs_to :billing_address, :class_name => ''Address'' end class Address < AR::B end The problem is that I want Address to be a Value Object (as in DDD), and if I do this: p = Person.create :shipping_address => Address.new(...) and later I change the address: p.shipping_address = Address.new(...) p.save the first address object doesn''t get deleted from the DB. It becomes an orphan. I can inverse the association and use a has_one but then I have to put two foreign keys in the address table... and that could be a problem because there are other models that have addresses. Another option could be to model both associations as composed_of but then I have to put all of the address table columns on the people table, and repeat this on the other models that have addresses too. How can I solve this? Any suggestions? Thanks in advance.
Hello, I think, you can to do it by using inheritance and STI for Address model class Person < AR::B has_one :shipping_address, :class_name => ''Address'' has_one :billing_address, :class_name => ''Address'' end class Address < AR::B belongs_to :person end class ShippingAddress < Address end class BillingAddress < Address end and add ''type'' column to address table But if you want to use one address for shipping and billing, it may be hard in this way. On Jul 25, 2:33 am, Emma <gkilo...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> I''m having trouble with this model: > > class Person < AR::B > belongs_to :shipping_address, :class_name => ''Address'' > belongs_to :billing_address, :class_name => ''Address'' > end > > class Address < AR::B > end > > The problem is that I want Address to be a Value Object (as in DDD), > and if I do this: > p = Person.create :shipping_address => Address.new(...) > > and later I change the address: > p.shipping_address = Address.new(...) > p.save > > the first address object doesn''t get deleted from the DB. It becomes > an orphan. > > I can inverse the association and use a has_one but then I have to put > two foreign keys in the address table... and that could be a problem > because there are other models that have addresses. > > Another option could be to model both associations as composed_of but > then I have to put all of the address table columns on the people > table, and repeat this on the other models that have addresses too. > > How can I solve this? Any suggestions? > Thanks in advance.
shouldn''t it be: class Person < AR::B has_one :shipping_address, :class_name => ''ShippingAddress'' has_one :billing_address, :class_name => ''BillingAddress'' end ? On Jul 25, 10:05 am, Rakoth <rakot...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Hello, I think, you can to do it by using inheritance and STI for > Address model > > class Person < AR::B > has_one :shipping_address, :class_name => ''Address'' > has_one :billing_address, :class_name => ''Address'' > end > > class Address < AR::B > belongs_to :person > end > > class ShippingAddress < Address > end > > class BillingAddress < Address > end > > and add ''type'' column to address table > > But if you want to use one address for shipping and billing, it may be > hard in this way. > > On Jul 25, 2:33 am, Emma <gkilo...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > > I''m having trouble with this model: > > > class Person < AR::B > > belongs_to :shipping_address, :class_name => ''Address'' > > belongs_to :billing_address, :class_name => ''Address'' > > end > > > class Address < AR::B > > end > > > The problem is that I want Address to be a Value Object (as in DDD), > > and if I do this: > > p = Person.create :shipping_address => Address.new(...) > > > and later I change the address: > > p.shipping_address = Address.new(...) > > p.save > > > the first address object doesn''t get deleted from the DB. It becomes > > an orphan. > > > I can inverse the association and use a has_one but then I have to put > > two foreign keys in the address table... and that could be a problem > > because there are other models that have addresses. > > > Another option could be to model both associations as composed_of but > > then I have to put all of the address table columns on the people > > table, and repeat this on the other models that have addresses too. > > > How can I solve this? Any suggestions? > > Thanks in advance.- Hide quoted text - > > - Show quoted text -
Emma wrote: [...]> if I do this: > p = Person.create :shipping_address => Address.new(...) > > and later I change the address: > p.shipping_address = Address.new(...) > p.save > > the first address object doesn''t get deleted from the DB. It becomes > an orphan.Right -- because there''s nothing in your code saying that the first address should be deleted. How is Rails to know that you don''t want to have the first Address available? [...]> > Another option could be to model both associations as composed_of but > then I have to put all of the address table columns on the people > table, and repeat this on the other models that have addresses too.I think this is actually the correct approach. As far as the schema is concerned, you don''t really want a separate Address table. If the DB supported Address columns, of course you''d do it that way; since it doesn''t, composed_of fakes this functionality for you. You can use modules or AR subclasses to cut down on code repetition. Alternatively, you could write a method that creates a new Address and deletes the old one explicitly.> > How can I solve this? Any suggestions? > Thanks in advance.Best, -- Marnen Laibow-Koser http://www.marnen.org marnen-sbuyVjPbboAdnm+yROfE0A@public.gmane.org -- Posted via http://www.ruby-forum.com/.
Marnen Laibow-Koser wrote:> If the DB > supported Address columns, of course you''d do it that way; since it > doesn''t, composed_of fakes this functionality for you.It occurs to me that you actually can do this with a single address column in the DB -- just use serialize. And that way you don''t have to worry about a repetitive schema. Best, -- Marnen Laibow-Koser http://www.marnen.org marnen-sbuyVjPbboAdnm+yROfE0A@public.gmane.org -- Posted via http://www.ruby-forum.com/.
On Saturday 25 July 2009, Emma wrote:> I''m having trouble with this model: > > class Person < AR::B > belongs_to :shipping_address, :class_name => ''Address'' > belongs_to :billing_address, :class_name => ''Address'' > end > > class Address < AR::B > end > > The problem is that I want Address to be a Value Object (as in DDD), > and if I do this: > p = Person.create :shipping_address => Address.new(...) > > and later I change the address: > p.shipping_address = Address.new(...) > p.save > > the first address object doesn''t get deleted from the DB. It becomes > an orphan.I''d try to handle a case like this in a callback. ActiveRecord automatically generates methods like #shipping_address_id_changed?, but that does only half the job in this particular case, because assigning an unsaved object (such as Address.new) to a belongs_to association does not change the existing foreign key immediately. class Person < AR::B belongs_to :shipping_address, :class_name => ''Address'' belongs_to :billing_address, :class_name => ''Address'' protected def before_save if shipping_address_id_changed? || shipping_address && (shipping_address_id != shipping_address.id) Address.delete(shipping_address_id_was) if shipping_address_id_was end # same for billing_address; better extract the common code end end The code probably won''t work as is, but it might get you started. Also, have a look at the :autosave option for belongs_to. HTH, Michael -- Michael Schuerig mailto:michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org http://www.schuerig.de/michael/
You can also use the methods included from ActiveRecord::Dirty on the foreign key fields; in your case, you''d have an after_save callback like this (on Person): after_save :cleanup_addresses def cleanup_addresses Address.destroy(shipping_address_id_was) if shipping_address_id_changed? && shipping_address_id_was Address.destroy(billing_address_id_was) if billing_address_id_changed? && billing_address_id_was end Some notes on this: - if you don''t have any callbacks or observers on Address, you can simplify the .destroy calls to .delete, and save instantiating some Address objects. - if your UI allows users to swap the addresses (ie, shipping_address_id is swapped with billing_address_id, without any new DB records), you''ll need to have a better check in cleanup_addresses; the current code will end up deleting both addresses in that case. --Matt Jones On Jul 24, 6:33 pm, Emma <gkilo...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> I''m having trouble with this model: > > class Person < AR::B > belongs_to :shipping_address, :class_name => ''Address'' > belongs_to :billing_address, :class_name => ''Address'' > end > > class Address < AR::B > end > > The problem is that I want Address to be a Value Object (as in DDD), > and if I do this: > p = Person.create :shipping_address => Address.new(...) > > and later I change the address: > p.shipping_address = Address.new(...) > p.save > > the first address object doesn''t get deleted from the DB. It becomes > an orphan. > > I can inverse the association and use a has_one but then I have to put > two foreign keys in the address table... and that could be a problem > because there are other models that have addresses. > > Another option could be to model both associations as composed_of but > then I have to put all of the address table columns on the people > table, and repeat this on the other models that have addresses too. > > How can I solve this? Any suggestions? > Thanks in advance.
On Saturday 25 July 2009, Matt Jones wrote:> You can also use the methods included from ActiveRecord::Dirty on the > foreign key fields; in your case, you''d have an after_save callback > like this (on Person): > > after_save :cleanup_addresses > > def cleanup_addresses > Address.destroy(shipping_address_id_was) if > shipping_address_id_changed? && shipping_address_id_was > Address.destroy(billing_address_id_was) if > billing_address_id_changed? && billing_address_id_was > endMatt, have you tried this code? Specifically, are you sure that the dirty states have not already been reset by the time the after_save callback is invoked? That was my concern when I suggested using a before_save callback in a parallel post. Michael -- Michael Schuerig mailto:michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org http://www.schuerig.de/michael/
I''m sure - I''ve got a big chunk of code using _changed? in after_save out in production. The flags are reset after the save operation completes - note that you can still rollback a save in the after_save callbacks by raising an exception. --Matt Jones On Jul 25, 5:17 pm, Michael Schuerig <mich...-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org> wrote:> On Saturday 25 July 2009, Matt Jones wrote: > > > You can also use the methods included from ActiveRecord::Dirty on the > > foreign key fields; in your case, you''d have an after_save callback > > like this (on Person): > > > after_save :cleanup_addresses > > > def cleanup_addresses > > Address.destroy(shipping_address_id_was) if > > shipping_address_id_changed? && shipping_address_id_was > > Address.destroy(billing_address_id_was) if > > billing_address_id_changed? && billing_address_id_was > > end > > Matt, have you tried this code? Specifically, are you sure that the > dirty states have not already been reset by the time the after_save > callback is invoked? That was my concern when I suggested using a > before_save callback in a parallel post. > > Michael > > -- > Michael Schuerig > mailto:mich...-q5aiKMLteq5BV9CJdY2HSA@public.gmane.org://www.schuerig.de/michael/
Guys, thank you very much for all your answers! I finally used Matt''s solution because the Address model have an association. It works great. On 25 jul, 14:36, Matt Jones <al2o...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> You can also use the methods included from ActiveRecord::Dirty on the > foreign key fields; in your case, you''d have an after_save callback > like this (on Person): > > after_save :cleanup_addresses > > def cleanup_addresses > Address.destroy(shipping_address_id_was) if > shipping_address_id_changed? && shipping_address_id_was > Address.destroy(billing_address_id_was) if > billing_address_id_changed? && billing_address_id_was > end > > Some notes on this: > > - if you don''t have any callbacks or observers on Address, you can > simplify the .destroy calls to .delete, and save instantiating some > Address objects. > - if your UI allows users to swap the addresses (ie, > shipping_address_id is swapped with billing_address_id, without any > new DB records), you''ll need to have a better check in > cleanup_addresses; the current code will end up deleting both > addresses in that case. > > --Matt Jones > > On Jul 24, 6:33 pm, Emma <gkilo...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > I''m having trouble with this model: > > > class Person < AR::B > > belongs_to :shipping_address, :class_name => ''Address'' > > belongs_to :billing_address, :class_name => ''Address'' > > end > > > class Address < AR::B > > end > > > The problem is that I want Address to be a Value Object (as in DDD), > > and if I do this: > > p = Person.create :shipping_address => Address.new(...) > > > and later I change the address: > > p.shipping_address = Address.new(...) > > p.save > > > the first address object doesn''t get deleted from the DB. It becomes > > an orphan. > > > I can inverse the association and use a has_one but then I have to put > > two foreign keys in the address table... and that could be a problem > > because there are other models that have addresses. > > > Another option could be to model both associations as composed_of but > > then I have to put all of the address table columns on the people > > table, and repeat this on the other models that have addresses too. > > > How can I solve this? Any suggestions? > > Thanks in advance. > >