require File.join(File.dirname(__FILE__), "spec_helper") describe "Model hooks" do before do MODEL_DB.reset end specify "should be definable using def " do c = Class.new(Sequel::Model) do def before_save "hi there" end end c.new.before_save.should == 'hi there' end specify "should be definable using a block" do $adds = [] c = Class.new(Sequel::Model) c.class_eval do before_save {$adds << 'hi'} end c.new.before_save $adds.should == ['hi'] end specify "should be definable using a method name" do $adds = [] c = Class.new(Sequel::Model) c.class_eval do def bye; $adds << 'bye'; end before_save :bye end c.new.before_save $adds.should == ['bye'] end specify "should be additive" do $adds = [] c = Class.new(Sequel::Model) c.class_eval do after_save {$adds << 'hyiyie'} after_save {$adds << 'byiyie'} end c.new.after_save $adds.should == ['hyiyie', 'byiyie'] end specify "before hooks should run in reverse order" do $adds = [] c = Class.new(Sequel::Model) c.class_eval do before_save {$adds << 'hyiyie'} before_save {$adds << 'byiyie'} end c.new.before_save $adds.should == ['byiyie', 'hyiyie'] end specify "should not be additive if the method or tag already exists" do $adds = [] c = Class.new(Sequel::Model) c.class_eval do def bye; $adds << 'bye'; end before_save :bye before_save :bye end c.new.before_save $adds.should == ['bye'] $adds = [] d = Class.new(Sequel::Model) d.class_eval do before_save(:bye){$adds << 'hyiyie'} before_save(:bye){$adds << 'byiyie'} end d.new.before_save $adds.should == ['byiyie'] $adds = [] e = Class.new(Sequel::Model) e.class_eval do def bye; $adds << 'bye'; end before_save :bye before_save(:bye){$adds << 'byiyie'} end e.new.before_save $adds.should == ['byiyie'] $adds = [] e = Class.new(Sequel::Model) e.class_eval do def bye; $adds << 'bye'; end before_save(:bye){$adds << 'byiyie'} before_save :bye end e.new.before_save $adds.should == ['bye'] end specify "should be inheritable" do # pending $adds = [] a = Class.new(Sequel::Model) a.class_eval do after_save {$adds << '123'} end b = Class.new(a) b.class_eval do after_save {$adds << '456'} after_save {$adds << '789'} end b.new.after_save $adds.should == ['123', '456', '789'] end specify "should be overridable in descendant classes" do $adds = [] a = Class.new(Sequel::Model) a.class_eval do before_save {$adds << '123'} end b = Class.new(a) b.class_eval do def before_save; $adds << '456'; end end a.new.before_save $adds.should == ['123'] $adds = [] b.new.before_save $adds.should == ['456'] end specify "should stop processing if a hook returns false" do $flag = true $adds = [] a = Class.new(Sequel::Model) a.class_eval do after_save {$adds << 'blah'; $flag} after_save {$adds << 'cruel'} end a.new.after_save $adds.should == ['blah', 'cruel'] # chain should not break on nil $adds = [] $flag = nil a.new.after_save $adds.should == ['blah', 'cruel'] $adds = [] $flag = false a.new.after_save $adds.should == ['blah'] b = Class.new(a) b.class_eval do after_save {$adds << 'mau'} end $adds = [] b.new.after_save $adds.should == ['blah'] end end describe "Model#after_initialize" do specify "should be called after initialization" do $values1 = nil $reached_after_initialized = false a = Class.new(Sequel::Model) a.class_eval do columns :x, :y after_initialize do $values1 = @values.clone $reached_after_initialized = true end end a.new(:x => 1, :y => 2) $values1.should == {:x => 1, :y => 2} $reached_after_initialized.should == true end end describe "Model#before_create && Model#after_create" do setup do MODEL_DB.reset @c = Class.new(Sequel::Model(:items)) @c.class_eval do columns :x no_primary_key after_create {MODEL_DB << "BLAH after"} end end specify "should be called around new record creation" do @c.before_create {MODEL_DB << "BLAH before"} @c.create(:x => 2) MODEL_DB.sqls.should == [ 'BLAH before', 'INSERT INTO items (x) VALUES (2)', 'BLAH after' ] end specify ".create should cancel the save and raise an error if before_create returns false and raise_on_save_failure is true" do @c.before_create{false} proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed) proc{@c.create(:x => 2)}.should raise_error(Sequel::BeforeHookFailed) MODEL_DB.sqls.should == [] end specify ".create should cancel the save and return nil if before_create returns false and raise_on_save_failure is false" do @c.before_create{false} @c.raise_on_save_failure = false @c.create(:x => 2).should == nil MODEL_DB.sqls.should == [] end end describe "Model#before_update && Model#after_update" do setup do MODEL_DB.reset @c = Class.new(Sequel::Model(:items)) @c.class_eval do after_update {MODEL_DB << "BLAH after"} end end specify "should be called around record update" do @c.before_update {MODEL_DB << "BLAH before"} m = @c.load(:id => 2233) m.save MODEL_DB.sqls.should == [ 'BLAH before', 'UPDATE items SET id = 2233 WHERE (id = 2233)', 'BLAH after' ] end specify "#save should cancel the save and raise an error if before_update returns false and raise_on_save_failure is true" do @c.before_update{false} proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed) proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed) MODEL_DB.sqls.should == [] end specify "#save should cancel the save and return nil if before_update returns false and raise_on_save_failure is false" do @c.before_update{false} @c.raise_on_save_failure = false @c.load(:id => 2233).save.should == nil MODEL_DB.sqls.should == [] end end describe "Model#before_save && Model#after_save" do setup do MODEL_DB.reset @c = Class.new(Sequel::Model(:items)) @c.class_eval do columns :x after_save {MODEL_DB << "BLAH after"} end end specify "should be called around record update" do @c.before_save {MODEL_DB << "BLAH before"} m = @c.load(:id => 2233) m.save MODEL_DB.sqls.should == [ 'BLAH before', 'UPDATE items SET id = 2233 WHERE (id = 2233)', 'BLAH after' ] end specify "should be called around record creation" do @c.before_save {MODEL_DB << "BLAH before"} @c.no_primary_key @c.create(:x => 2) MODEL_DB.sqls.should == [ 'BLAH before', 'INSERT INTO items (x) VALUES (2)', 'BLAH after' ] end specify "#save should cancel the save and raise an error if before_save returns false and raise_on_save_failure is true" do @c.before_save{false} proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed) proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed) MODEL_DB.sqls.should == [] end specify "#save should cancel the save and return nil if before_save returns false and raise_on_save_failure is false" do @c.before_save{false} @c.raise_on_save_failure = false @c.load(:id => 2233).save.should == nil MODEL_DB.sqls.should == [] end end describe "Model#before_destroy && Model#after_destroy" do setup do MODEL_DB.reset @c = Class.new(Sequel::Model(:items)) @c.class_eval do after_destroy {MODEL_DB << "BLAH after"} def delete MODEL_DB << "DELETE BLAH" end end end specify "should be called around record destruction" do @c.before_destroy {MODEL_DB << "BLAH before"} m = @c.load(:id => 2233) m.destroy MODEL_DB.sqls.should == [ 'BLAH before', 'DELETE BLAH', 'BLAH after' ] end specify "#destroy should cancel the destroy and raise an error if before_destroy returns false and raise_on_save_failure is true" do @c.before_destroy{false} proc{@c.load(:id => 2233).destroy}.should raise_error(Sequel::BeforeHookFailed) MODEL_DB.sqls.should == [] end specify "#destroy should cancel the destroy and return nil if before_destroy returns false and raise_on_save_failure is false" do @c.before_destroy{false} @c.raise_on_save_failure = false @c.load(:id => 2233).destroy.should == nil MODEL_DB.sqls.should == [] end end describe "Model#before_validation && Model#after_validation" do setup do MODEL_DB.reset @c = Class.new(Sequel::Model(:items)) @c.class_eval do after_validation{MODEL_DB << "BLAH after"} def self.validate(o) o.errors[:id] << 'not valid' unless o[:id] == 2233 end def save!(*columns) MODEL_DB << "CREATE BLAH" self end columns :id end end specify "should be called around validation" do @c.before_validation{MODEL_DB << "BLAH before"} m = @c.load(:id => 2233) m.should be_valid MODEL_DB.sqls.should == ['BLAH before', 'BLAH after'] MODEL_DB.sqls.clear m = @c.load(:id => 22) m.should_not be_valid MODEL_DB.sqls.should == ['BLAH before', 'BLAH after'] end specify "should be called when calling save" do @c.before_validation{MODEL_DB << "BLAH before"} m = @c.load(:id => 2233) m.save.should == m MODEL_DB.sqls.should == ['BLAH before', 'BLAH after', 'CREATE BLAH'] MODEL_DB.sqls.clear m = @c.load(:id => 22) m.raise_on_save_failure = false m.save.should == nil MODEL_DB.sqls.should == ['BLAH before', 'BLAH after'] end specify "#save should cancel the save and raise an error if before_validation returns false and raise_on_save_failure is true" do @c.before_validation{false} proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed) proc{@c.load(:id => 2233).save}.should raise_error(Sequel::BeforeHookFailed) MODEL_DB.sqls.should == [] end specify "#save should cancel the save and return nil if before_validation returns false and raise_on_save_failure is false" do @c.before_validation{false} @c.raise_on_save_failure = false @c.load(:id => 2233).save.should == nil MODEL_DB.sqls.should == [] end end describe "Model.has_hooks?" do setup do @c = Class.new(Sequel::Model(:items)) end specify "should return false if no hooks are defined" do @c.has_hooks?(:before_save).should be_false end specify "should return true if hooks are defined" do @c.before_save {'blah'} @c.has_hooks?(:before_save).should be_true end specify "should return true if hooks are inherited" do @d = Class.new(@c) @d.has_hooks?(:before_save).should be_false end end describe "Model#add_hook_type" do setup do class Foo < Sequel::Model(:items) add_hook_type :before_bar, :after_bar def bar return :b if before_bar == false return :a if after_bar == false true end end @f = Class.new(Foo) end specify "should have before_bar and after_bar class methods" do @f.should respond_to(:before_bar) @f.should respond_to(:before_bar) end specify "should have before_bar and after_bar instance methods" do @f.new.should respond_to(:before_bar) @f.new.should respond_to(:before_bar) end specify "it should return true for bar when before_bar and after_bar hooks are returing true" do a = 1 @f.before_bar { a += 1} @f.new.bar.should be_true a.should == 2 @f.after_bar { a *= 2} @f.new.bar.should be_true a.should == 6 end specify "it should return nil for bar when before_bar and after_bar hooks are returing false" do @f.new.bar.should be_true @f.after_bar { false } @f.new.bar.should == :a @f.before_bar { false } @f.new.bar.should == :b end end