Reputation: 3963
I'm working in a large Rails 2.3 application and I have data on a model that would like to move to another model. I need to do this is phases as there are places in the Rails code base that are reading and writing this model data and outside applications reading the table data directly via SQL. I need to allow a period of time where the attribute is synchronized on both models and their associated tables before I drop one model and table altogether.
My models have a has_one
and belongs_to
relationship like this:
class User < ActiveRecord::Base
has_one :user_email, :inverse_of => :user
accepts_nested_attributes_for :user_email
validates_presence_of :email
def email=( value )
write_attribute(:email, value)
user_email.write_attribute(:email, value)
end
end
class UserEmail < ActiveRecord::Base
belongs_to :user, :inverse_of => :user_email
validates_presence_of :email
def email=( value )
write_attribute(:email, value)
user.write_attribute(:email, value)
end
end
I'd like to do away with UserEmail
and its associated table altogether, but for a time I need to keep email
up-to-date on both models so if it's set on one model, it's changed on the other. Overriding email=
on each model is straightforward, but coming up with a commit strategy is where I'm hitting a wall.
I have places in the code base that are doing things like:
user.user_email.save!
and I'm hoping to find a way to continue to allow this kind of code for the time being.
I can't figure out a way to ensure that saving an instance of User
ensures the corresponding UserEmail
data is committed and saving an instance of UserEmail
ensures the corresponding User
instance data is also committed without creating an infinite save loop in the call backs.
This is the flow I would like to be able to support for the time being:
params = { user: { email: '[email protected]', user_email: { email: '[email protected]' } } }
user = User.create( params )
user.email = "[email protected]"
user.save
puts user.user_email # puts "[email protected]"
user.user_email.email = "[email protected]"
user.user_email.save
user.reload
puts user.email # puts "[email protected]"
Is there a way to achieve this sort of synchronization between the User
and UserEmail
models so they are kept in sync?
If it helps, I can probably do away with accepts_nested_attributes_for :user_email
on User
.
Upvotes: 0
Views: 2206
Reputation: 457
In User model
after_save :sync_email, :if => :email_changed?
def sync_email
user_email.update_column(:email, email) if user_email.email != email
end
In UserEmail model
after_save :sync_email, :if => :email_changed?
def sync_email
user.update_column(:email, email) if user.email != email
end
Upvotes: 4
Reputation: 32945
Let's assume, for sanity's sake, that the models are "User" and "Cart", and the shared field is "email". I would do this:
#in User
after_save :update_cart_email
def update_cart_email
if self.changes["email"]
cart = self.cart
if cart.email != self.email
cart.update_attributes(:email => self.email)
end
end
end
#in Cart
after_save :update_user_email
def update_user_email
if self.changes["email"]
user = self.user
if user.email != self.email
user.update_attributes(:email => user.email)
end
end
end
Because we check if the other model's email has already been set, it shouldn't get stuck in a loop.
This works if you drop accepts_nested_attributes_for :user_email
-- otherwise you'll get a save loop that never ends.
Upvotes: 3