Skip to content

Commit

Permalink
Implemented action callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
jodosha committed Jun 28, 2013
1 parent 8fe8763 commit 4bb794d
Show file tree
Hide file tree
Showing 7 changed files with 385 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/lotus/action.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
require 'lotus/action/exposable'
require 'lotus/action/callbacks'
require 'lotus/action/callable'

module Lotus
module Action
def self.included(base)
base.class_eval do
include Exposable
include Callbacks
prepend Callable
end
end
Expand Down
54 changes: 54 additions & 0 deletions lib/lotus/action/callbacks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'lotus/utils/class_attribute'
require 'lotus/utils/callbacks'

module Lotus
module Action
module Callbacks
def self.included(base)
base.class_eval do
extend ClassMethods
prepend InstanceMethods
end
end

module ClassMethods
def self.extended(base)
base.class_eval do
include Utils::ClassAttribute

class_attribute :before_callbacks
self.before_callbacks = Utils::Callbacks::Chain.new

class_attribute :after_callbacks
self.after_callbacks = Utils::Callbacks::Chain.new
end
end

def before(*callbacks, &blk)
before_callbacks.add *callbacks, &blk
end

def after(*callbacks, &blk)
after_callbacks.add *callbacks, &blk
end
end

module InstanceMethods
def call(params)
_run_before_callbacks(params)
super
_run_after_callbacks(params)
end

private
def _run_before_callbacks(params)
self.class.before_callbacks.run(self, params)
end

def _run_after_callbacks(params)
self.class.after_callbacks.run(self, params)
end
end
end
end
end
52 changes: 52 additions & 0 deletions lib/lotus/utils/callbacks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Lotus
module Utils
module Callbacks
class Chain < Set
def add(*callbacks, &blk)
callbacks.push blk if block_given?
callbacks.each do |c|
super Callback.fabricate(c)
end
end

def run(context, *args)
each do |callback|
callback.call(context, *args)
end
end
end

class Callback
attr_reader :callback

def self.fabricate(callback)
if callback.respond_to?(:call)
new(callback)
else
MethodCallback.new(callback)
end
end

def initialize(callback)
@callback = callback
end

def call(context, *args)
context.instance_exec(*args, &callback)
end
end

class MethodCallback < Callback
def call(context, *args)
method = context.method(callback)

if method.parameters.any?
method.call(*args)
else
method.call
end
end
end
end
end
end
31 changes: 31 additions & 0 deletions lib/lotus/utils/class_attribute.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Lotus
module Utils
module ClassAttribute
def self.included(base)
base.class_eval do
extend ClassMethods
end
end

module ClassMethods
def class_attribute(*names)
(class << self; self; end).class_eval do
attr_accessor *names
end

@class_attributes ||= Set.new
@class_attributes.merge(names)
end

def inherited(subclass)
@class_attributes.each do |attr|
value = send(attr).dup rescue nil
subclass.send("#{attr}=", value)
end

super
end
end
end
end
end
97 changes: 97 additions & 0 deletions test/action/callbacks_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require 'test_helper'

describe Lotus::Action do
describe '#before' do
it 'invokes the method(s) from the given symbol(s) before the action is run' do
action = BeforeMethodAction.new
action.call({})

action.article.must_equal 'Bonjour!'.reverse
end

it 'invokes the given block before the action is run' do
action = BeforeBlockAction.new
action.call({})

action.article.must_equal 'Good morning!'.reverse
end

it 'inherits callbacks from superclass' do
action = SubclassBeforeMethodAction.new
action.call({})

action.article.must_equal 'Bonjour!'.reverse.upcase
end

it 'can optionally have params in method signature' do
action = ParamsBeforeMethodAction.new
action.call(bang: '!')

action.article.must_equal 'Bonjour!!'.reverse
end

it 'yields params when the callback is a block' do
action = YieldBeforeBlockAction.new
action.call(params = { twentythree: '23' })

action.yielded_params.must_equal params
end

describe 'on error' do
it 'stops the callbacks execution and returns an HTTP 500 status' do
action = ErrorBeforeMethodAction.new
response = action.call({})

response[0].must_equal 500
action.article.must_be_nil
end
end
end

describe '#after' do
it 'invokes the method(s) from the given symbol(s) after the action is run' do
action = AfterMethodAction.new
action.call({})

action.egg.must_equal 'gE!g'
end

it 'invokes the given block after the action is run' do
action = AfterBlockAction.new
action.call({})

action.egg.must_equal 'Coque'.reverse
end

it 'inherits callbacks from superclass' do
action = SubclassAfterMethodAction.new
action.call({})

action.egg.must_equal 'gE!g'.upcase
end

it 'can optionally have params in method signature' do
action = ParamsAfterMethodAction.new
action.call(question: '?')

action.egg.must_equal 'gE!g?'
end

it 'yields params when the callback is a block' do
action = YieldAfterBlockAction.new
action.call(params = { fortytwo: '42' })

action.meaning_of_life_params.must_equal params
end

describe 'on error' do
it 'stops the callbacks execution and returns an HTTP 500 status' do
action = ErrorAfterMethodAction.new
response = action.call({})

response[0].must_equal 500
action.egg.must_be_nil
end
end
end
end
128 changes: 128 additions & 0 deletions test/fixtures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,131 @@ def call(params)
@film = '400 ASA'
end
end

class BeforeMethodAction
include Lotus::Action

expose :article
before :set_article, :reverse_article

def call(params)
end

private
def set_article
@article = 'Bonjour!'
end

def reverse_article
@article.reverse!
end
end

class SubclassBeforeMethodAction < BeforeMethodAction
before :upcase_article

private
def upcase_article
@article.upcase!
end
end

class ParamsBeforeMethodAction < BeforeMethodAction
private
def set_article(params)
@article = super() + params[:bang]
end
end

class ErrorBeforeMethodAction < BeforeMethodAction
private
def set_article
raise
end
end

class BeforeBlockAction
include Lotus::Action

expose :article
before { @article = 'Good morning!' }
before { @article.reverse! }

def call(params)
end
end

class YieldBeforeBlockAction < BeforeBlockAction
expose :yielded_params
before {|params| @yielded_params = params }
end

class AfterMethodAction
include Lotus::Action

expose :egg
after :set_egg, :scramble_egg

def call(params)
end

private
def set_egg
@egg = 'Egg!'
end

def scramble_egg
@egg = 'gE!g'
end
end

class SubclassAfterMethodAction < AfterMethodAction
after :upcase_egg

private
def upcase_egg
@egg.upcase!
end
end

class ParamsAfterMethodAction < AfterMethodAction
private
def scramble_egg(params)
@egg = super() + params[:question]
end
end

class ErrorAfterMethodAction < AfterMethodAction
private
def set_egg
raise
end
end

class AfterBlockAction
include Lotus::Action

expose :egg
after { @egg = 'Coque' }
after { @egg.reverse! }

def call(params)
end
end

class YieldAfterBlockAction < AfterBlockAction
expose :meaning_of_life_params
before {|params| @meaning_of_life_params = params }
end

class ClassAttributeTest
include Lotus::Utils::ClassAttribute

class_attribute :callbacks, :functions, :values
self.callbacks = [:a]
self.values = [1]
end

class SubclassAttributeTest < ClassAttributeTest
self.functions = [:x, :y]
end
Loading

0 comments on commit 4bb794d

Please sign in to comment.