Skip to content

Commit

Permalink
Ensure callbacks are propagated to embedded associated documents.
Browse files Browse the repository at this point in the history
If an embedded child document has a before_save hook, we need to ensure this runs when the parent document is saved.
  • Loading branch information
myronmarston committed May 12, 2011
1 parent c99f284 commit 2b04581
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 13 deletions.
22 changes: 22 additions & 0 deletions ripple/lib/ripple/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,28 @@ def attributes_for_persistence
attrs
end
end

# Propagates callbacks (save/create/update/destroy) to embedded associated documents.
# This is necessary so that when a parent is saved, the embedded child's before_save
# hooks are run as well.
# @private
def run_callbacks(*args, &block)
self.class.embedded_associations.each do |association|
documents = instance_variable_get(association.ivar)
# We must explicitly check #nil? (rather than just saying `if documents`)
# because documents can be an association proxy that is proxying nil.
# In this case ruby treats documents as true because it is not _really_ nil,
# but #nil? will tell us if it is proxying nil.

unless documents.nil?
Array(documents).each do |doc|
doc.run_callbacks(*args, &block)
end
end
end

super
end
end
end

Expand Down
10 changes: 10 additions & 0 deletions ripple/spec/ripple/callbacks_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@
@box.save
end

it "propagates callbacks to embedded associated documents" do
Box.before_save { $pinger.ping }
BoxSide.before_save { $pinger.ping }
$pinger.should_receive(:ping).exactly(2).times
@box = Box.new
@box.sides << BoxSide.new
@box.save
end

it "should call create callbacks on save when the document is new" do
Box.before_create { $pinger.ping }
Box.after_create { $pinger.ping }
Expand Down Expand Up @@ -120,6 +129,7 @@
after :each do
[:save, :create, :update, :destroy, :validation].each do |type|
Box.reset_callbacks(type)
BoxSide.reset_callbacks(type)
end
end
end
Expand Down
40 changes: 27 additions & 13 deletions ripple/spec/ripple/timestamps_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@
end

it "sets the updated_at timestamp when the object is saved" do
subject.save
record_to_save.save
subject.updated_at.should_not be_nil
end

it "updates the updated_at timestamp when the object is updated" do
subject.save
record_to_save.save
start = subject.updated_at
subject.save
record_to_save.save
subject.updated_at.should > start
end

it "does not update the created_at timestamp when the object is updated" do
subject.save
record_to_save.save
start = subject.created_at
subject.save
record_to_save.save
subject.created_at.should == start
end
end
Expand All @@ -44,19 +44,33 @@
before(:each) { Ripple.client.stub!(:backend).and_return(backend) }

context "for a Ripple::Document" do
subject { Clock.new }
it_behaves_like "a timestamped model"
it_behaves_like "a timestamped model" do
subject { Clock.new }
let(:record_to_save) { subject }
end
end

context "for a Ripple::EmbeddedDocument" do
let(:clock) { Clock.new }
context "for a Ripple::EmbeddedDocument when directly saved" do
it_behaves_like "a timestamped model" do
let(:clock) { Clock.new }
subject { Mode.new }
let(:record_to_save) { subject }

subject do
Mode.new.tap do |m|
clock.modes << m
before(:each) do
clock.modes << subject
end
end
end

context "for a Ripple::EmbeddedDocument when the parent is saved" do
it_behaves_like "a timestamped model" do
let(:clock) { Clock.new }
subject { Mode.new }
let(:record_to_save) { clock }

it_behaves_like "a timestamped model"
before(:each) do
clock.modes << subject
end
end
end
end
7 changes: 7 additions & 0 deletions ripple/spec/support/models/box.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,12 @@
class Box
include Ripple::Document
property :shape, String
many :sides, :class_name => 'BoxSide'
timestamps!
end

class BoxSide
include Ripple::EmbeddedDocument
embedded_in :box
property :material, String
end

0 comments on commit 2b04581

Please sign in to comment.