Permalink
Browse files

Version bump to 0.0.0

  • Loading branch information...
1 parent f23b6be commit e22673ea658005672af582317e17252912440c43 @tadman committed Aug 25, 2010
Showing with 423 additions and 21 deletions.
  1. +1 −0 .gitignore
  2. +1 −1 LICENSE
  3. +80 −12 README.rdoc
  4. +5 −5 Rakefile
  5. +1 −0 VERSION
  6. +170 −0 lib/sequel_simple_callbacks.rb
  7. +4 −1 test/helper.rb
  8. +45 −0 test/models/conditional.rb
  9. +26 −0 test/models/example.rb
  10. +18 −0 test/models/model_triggers.rb
  11. +72 −2 test/test_sequel_simple_callbacks.rb
View
@@ -19,3 +19,4 @@ rdoc
pkg
## PROJECT::SPECIFIC
+tmp
View
@@ -1,4 +1,4 @@
-Copyright (c) 2009 tadman
+Copyright (c) 2010 Scott Tadman, The Working Group
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
View
@@ -1,17 +1,85 @@
= sequel_simple_callbacks
-Description goes here.
-
-== Note on Patches/Pull Requests
-
-* Fork the project.
-* Make your feature addition or bug fix.
-* Add tests for it. This is important so I don't break it in a
- future version unintentionally.
-* Commit, do not mess with rakefile, version, or history.
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-* Send me a pull request. Bonus points for topic branches.
+This adds ActiveRecord style callback declarations to standard Sequel models.
+
+Sequel::Model with no plugins:
+
+ class MyModel < Sequel::Model
+ def before_validation
+ check_something
+ check_something_else
+ end
+
+ def before_save
+ return unless (some_condition?)
+
+ do_something
+ end
+ end
+
+Sequel::Model with SequelSimpleCallbacks plugin added:
+
+ Sequel::Model.plugin(SequelSimpleCallbacks)
+
+ class MyModel < Sequel::Model
+ before_validation :check_something, :check_something_else
+
+ before_save :do_something, :unless => :some_condition?
+ end
+
+If any of the callbacks returns false then additional checking will be
+pre-empted and the callback will return false. This will halt processing
+of not only the chain, but the entire operation being performed, as is the
+expected behavior of Sequel::Model.
+
+Each of the callback methods takes zero or more method names to call as
+part of that callback cycle:
+
+ before_save :method_1, :method_2
+
+The execution of these methods can be limited conditionally using the
+:if or :unless options either independently or in tandem:
+
+ before_save :method_1, :if => :working?, :unless => :on_break?
+
+The blocks referenced by :if and :unless should return true or false,
+but any value that evaluates as false for :if or true for :unless will
+block execution of these callbacks. Note that this does not halt the
+callback chain.
+
+These arguments can be combined as demonstrated here:
+
+ before_save :method_1, :method_2,
+ :if => :method,
+ :unless => lambda { other_method },
+ :on => :create do
+ check_something
+ end
+
+Any blocks given are evaluated within the context of the model in question,
+but the model may be explicitly specified as a parameter to the block:
+
+ before_save do |model|
+ model.check_something
+ end
+
+It is important to node that using these class-level declarations means that
+the instance methods with the same name should not be defined:
+
+ before_save :do_something
+
+ def before_save
+ # WARNING: This will block the :do_something method from running,
+ # as this method over-rides that behavior. Calling super will not
+ # restore this functionality.
+
+ do_some_stuff
+
+ # Execute the default behavior as defined in the class if this mixed
+ # approach is strictly required. This is intended as a last-resort.
+ self.class.run_callbacks(self, :before_save)
+ end
== Copyright
-Copyright (c) 2010 tadman. See LICENSE for details.
+Copyright (c) 2010 Scott Tadman, The Working Group
View
@@ -5,13 +5,13 @@ begin
require 'jeweler'
Jeweler::Tasks.new do |gem|
gem.name = "sequel_simple_callbacks"
- gem.summary = %Q{TODO: one-line summary of your gem}
- gem.description = %Q{TODO: longer description of your gem}
+ gem.summary = %Q[Sequel Plugin to add ActiveRecord style callback declarations]
+ gem.description = %Q[This plugin makes it possible to declare simple before and after callbacks on the class level just like ActiveRecord]
gem.email = "github@tadman.ca"
gem.homepage = "http://github.com/tadman/sequel_simple_callbacks"
- gem.authors = ["tadman"]
- gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
+ gem.authors = %w[ tadman ]
+
+ gem.add_development_dependency "sequel"
end
Jeweler::GemcutterTasks.new
rescue LoadError
View
@@ -0,0 +1 @@
+0.0.0
@@ -0,0 +1,170 @@
+module SimpleSequelCallbacks
+ SPECIAL_HOOKS = [
+ :before_validation,
+ :after_validation
+ ].freeze
+
+ ADDITIONAL_HOOKS = [
+ :before_validation_on_create,
+ :before_validation_on_update,
+ :after_validation_on_create,
+ :after_validation_on_update
+ ].freeze
+
+ STANDARD_HOOKS = (Sequel::Model::HOOKS - SPECIAL_HOOKS).freeze
+ INSTALLABLE_HOOKS = (Sequel::Model::HOOKS + ADDITIONAL_HOOKS).freeze
+
+ def apply(model_class)
+ end
+
+ def configure(model_class, *arguments, &block)
+ end
+
+ module ClassMethods
+ # Add a callback hook to the model with parameters:
+ # * :if => One of [ Symbol, Proc ] (optional)
+ # * :unless => One of [ Symbol, Proc ] (optional)
+ # * :on => One of [ :create, :update ] (optional)
+
+ def add_callback(chain, *args, &block)
+ @callbacks ||= { }
+ callbacks = @callbacks[chain] ||= [ ]
+
+ # Extract the options from the arguments by testing if the last
+ # is a Hash, otherwise default to an empty set.
+ options = (args[-1].is_a?(Hash)) ? args.pop : { }
+ option_on = options[:on]
+ option_if = options[:if]
+ option_unless = options[:unless]
+
+ callbacks << lambda do |model|
+ result = nil
+
+ trigger =
+ case (option_on)
+ when :create
+ model.new?
+ when :update
+ !model.new?
+ else
+ true
+ end
+
+ if (trigger and !option_if.nil?)
+ trigger =
+ case (option_if)
+ when Symbol, String
+ model.send(option_if)
+ when Proc
+ if (option_if.arity == 0)
+ model.instance_eval(option_if)
+ else
+ option_if.call(model)
+ end
+ else
+ option_if
+ end
+ end
+
+ if (trigger and !option_unless.nil?)
+ trigger =
+ case (option_unless)
+ when Symbol, String
+ !model.send(option_unless)
+ when Proc
+ if (option_unless.arity == 0)
+ !model.instance_eval(option_unless)
+ else
+ !option_unless.call(model)
+ end
+ else
+ !option_unless
+ end
+ end
+
+ if (trigger)
+ args.each do |callback|
+ result =
+ case (callback)
+ when Symbol, String
+ model.send(callback)
+ else
+ if (callback.arity == 0)
+ model.instance_eval(callback)
+ else
+ callback.call(model)
+ end
+ end
+
+ break if (result === false)
+ end
+ end
+
+ if (trigger and block)
+ if (block.arity == 0)
+ model.instance_eval(&block)
+ else
+ block.call(model)
+ end
+ end
+
+ result
+ end
+ end
+
+ def run_callbacks(model, hook)
+ return unless (@callbacks)
+
+ callbacks = @callbacks[hook]
+
+ return unless (callbacks)
+
+ result = nil
+
+ callbacks.each do |callback|
+ result = callback.call(model)
+
+ break if (result === false)
+ end
+
+ result
+ end
+
+ INSTALLABLE_HOOKS.each do |hook|
+ eval %Q[
+ def #{hook}(*args, &block)
+ add_callback(:#{hook}, *args, &block)
+ end
+ ]
+ end
+ end
+
+ module InstanceMethods
+ STANDARD_HOOKS.each do |hook|
+ define_method(hook) do
+ self.class.run_callbacks(self, hook)
+ end
+ end
+
+ SPECIAL_HOOKS.each do |hook|
+ define_method(hook) do
+ self.class.run_callbacks(self, hook)
+
+ if (new?)
+ self.class.run_callbacks(self, :"#{hook}_on_create")
+ else
+ self.class.run_callbacks(self, :"#{hook}_on_update")
+ end
+ end
+ end
+
+ # This method is provided as a simple method to call arbitrary callback
+ # chains without having to run through the specific method
+ def run_callbacks(hook)
+ self.class.run_callbacks(self, hook)
+ end
+ end
+
+ module DatasetMethods
+ end
+end
View
@@ -1,9 +1,12 @@
require 'rubygems'
require 'test/unit'
-require 'shoulda'
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
+
+require 'sequel'
+require 'sqlite3'
+
require 'sequel_simple_callbacks'
class Test::Unit::TestCase
@@ -0,0 +1,45 @@
+if (DB.table_exists?(:conditionals))
+ DB.drop_table(:conditionals)
+end
+
+DB.create_table(:conditionals) do
+ primary_key :id
+ string :name
+end
+
+class ConditionalModel < Sequel::Model(:conditionals)
+ include ModelTriggers
+ plugin SimpleSequelCallbacks
+
+ before_validation do |model|
+ model.trigger(:before_validation_with_model)
+ end
+
+ before_validation do
+ trigger(:before_validation_without_model)
+ end
+
+ after_validation :on => :create do
+ trigger(:after_validation_only_on_create)
+ end
+
+ before_save :if => nil, :unless => nil do
+ trigger(:before_save_with_nil)
+ end
+
+ before_save :if => true, :unless => false do
+ trigger(:before_save_with_true_false)
+ end
+
+ after_update :only => :never do
+ trigger(:after_update_never_called)
+ end
+
+ after_save :if => false do
+ trigger(:after_save_not_called)
+ end
+
+ def never
+ false
+ end
+end
@@ -0,0 +1,26 @@
+if (DB.table_exists?(:examples))
+ DB.drop_table(:examples)
+end
+
+DB.create_table(:examples) do
+ primary_key :id
+ string :name
+end
+
+class ExampleModel < Sequel::Model(:examples)
+ include ModelTriggers
+
+ plugin SimpleSequelCallbacks
+
+ SimpleSequelCallbacks::INSTALLABLE_HOOKS.each do |hook|
+ send(hook, :"do_#{hook}", :if => :triggers_active?)
+
+ define_method(:"do_#{hook}") do
+ trigger(hook)
+ end
+ end
+
+ def triggers_active?
+ !name or name != 'off'
+ end
+end
Oops, something went wrong.

0 comments on commit e22673e

Please sign in to comment.