Skip to content

Commit

Permalink
Safely prepend Publisher initializers
Browse files Browse the repository at this point in the history
This change alters `Publisher` and `EventExaminer` to use
`Module#prepend` to inject their initializers into the call chain. The
intention is to allow classes that include `Publisher` to include their
own `#initialize` method that still preserves the behavior of
`Publisher#initialize`.

Consider:

    module Foo
      def initialize(*args)
        puts "Foo: #{args.inspect}"
        super
      end
    end

    class A
      include Foo
      def initialize(arg)
        puts "A: #{arg}"
      end
    end

    class B
      prepend Foo
      def initilaize(arg)
        puts "B: #{arg}"
      end
    end

In the case of `A`, since `Foo` is attached via `#include`, the
initializer defined in `Foo` is essentially ignored:

    irb(main):046:0> A.new :foo
    A: :foo
    => #<A:0x007fae191b4a08>

However, in `B`, since `Foo` is attacted via `#prepend`, the initializer
actually ends up behaving as if it were a superclass method of
`B#initialize`:

    irb(main):045:0> B.new :foo
    Foo: [:foo]
    B: :foo
    => #<B:0x007fae191bca28>

This `#prepend` trick works well for your initializers that actually
want to inject behavior during initialization, but ultimately stay out
of the way of any `#initialize` defined by the consuming class.

I ran into this when I tried including `Publisher` in a class that had
an initializer with arguments; I was seeing a "1 for 0" `ArgumentError`.
When I dug into the proofs, I noticed that `ReplayTest` does not
actually have an `#initialize` method of its' own.

My initial effort at producing a failing test was to subclass
`ReplayTest` within the specific proof for testing custom initializers;
however, subclassing overrode the faulty behavior and thus did not
produce a failing test. Instead, I added a constructor argument to
override the `pkey` attribute. It seemed like the least janky way to
introduce a failing test. If I made the proofs worse, I can try and
think of a better way to expose the problem I was trying to solve.

It's worth noting that this change would break ruby 1.9, which does not
have `Module#prepend`.
  • Loading branch information
ntl committed Mar 30, 2014
1 parent 4db060c commit 870291a
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 7 deletions.
9 changes: 6 additions & 3 deletions lib/replay/publisher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ def self.include_essentials(base)
base.instance_variable_set :@application_blocks, {}
base.extend ClassMethods
base.extend(Replay::Events)
base.send :prepend, Initializer
end

def initialize(*args)
@subscription_manager = Replay::SubscriptionManager.new(Replay.logger)
super
module Initializer
def initialize(*)
@subscription_manager = Replay::SubscriptionManager.new(Replay.logger)
super
end
end

def add_subscriber(subscriber)
Expand Down
12 changes: 9 additions & 3 deletions lib/replay/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@ def similar_events(event)
@_events.select{|e| e.class == event.class}
end

def initialize()
@_events ||= []
super
module Initializer
def initialize(*)
@_events ||= []
super
end
end

def self.extended(object)
object.instance_variable_set(:@_events, [])
end

def self.included(base)
base.send :prepend, Initializer
end

def apply(events, raise_unhandled = true)
return apply([events], raise_unhandled) unless events.is_a?(Array)
retval = super(events, raise_unhandled)
Expand Down
10 changes: 9 additions & 1 deletion proofs/replay/publisher_proof.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ class ReplayTest

key :pkey

def initialize(pkey = 1)
@pkey = pkey
end

events do
SomeEvent(pid: Integer)
UnhandledEvent(pid: Integer)
Expand All @@ -19,7 +23,7 @@ class ReplayTest
end

def pkey
1
@pkey
end
end

Expand Down Expand Up @@ -140,3 +144,7 @@ def published?; @published; end
r.prove{ publish([]) == self}
end

proof "Can implement initializer with arguments" do
r = ReplayTest.new(:foo)
r.prove { pkey == :foo }
end

0 comments on commit 870291a

Please sign in to comment.