Given something like:
class MyClass
def subscribe
$redis.subscribe('channel') do |on|
on.message do |channel, msg|
Something.create(msg)
end
end
end
end
How can I test that when MyClass executes subscribe, it will run Something.create for each message it receives on the channel?
This code you have, it's not very testable. First of all, absolutely get rid of this global $redis variable. Instead, accept an instance of redis in the constructor.
class MyClass
attr_reader :redis
def initialize(redis)
#redis = redis
end
def subscribe
redis.subscribe('channel') do |on|
on.message do |channel, msg|
Something.create(msg)
end
end
end
end
Then in tests you can make a dummy redis that you can totally control but which conforms to the api you're using. Something along these lines:
class DummyRedis
def subscribe(&block)
#block = block
end
def trigger_on
#block.call make_on_message
end
end
fake_redis = DummyRedis.new
expect {
mc = MyClass.new(fake_redis)
mc.subscribe
fake_redis.trigger_on
}.to change{Something.count}.from(0).to(1)
This cool technique is called Dependency Injection (or, as some people put it, "passing parameters to constructors").
Well, it could be as easy as
describe MyClass do
it 'should create something' do
expect(Something).to receive(:create)
subject.subscribe
subject.trigger_message # you should trigger a message somehow
end
end
Although this approach is not using actual tests, i would do the following and check the logs.
class MyClass
def subscribe
$redis.subscribe('channel') do |on|
on.message do |channel, msg|
event = Something.create(msg)
p event.persisted? ? "success" : "fail"
p event
end
end
end
end
Related
I've got an existing library comprised of many services which all respond to the method execute each method does it's logic
class BaseService
def execute
raise NotImplementedError
end
end
class CreateUserService < BaseService
def execute
# Some code that does Other stuff
end
end
class NotifyService < BaseService
def execute
# Some code that does other other stuff
end
end
I would like to do something to accomplish something like:
class BaseService
around :execute do |&block|
puts 'Before'
block.call
puts 'After'
end
end
Which then wraps every execute method including child classes so that logic can be performed before and after.
I've done something like this:
module Hooks
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def around(*symbols, &block)
to_prepend = build_module(symbols) do |*args, &mblock|
result = nil
block.call do
result = super(*args, &mblock)
end
result
end
prepend to_prepend
end
private
def build_module(symbols, &block)
Module.new do
symbols.each do |symbol|
define_method(symbol, &block)
end
end
end
end
end
class BaseService
include Hooks
around :execute do |&block|
puts 'before'
block.call
puts 'after'
end
# ..
end
However when the around method only gets executed on the base class. I'm guessing this is due to the nature of prepend. The ancestral order looks like:
[<Module>, BaseService, Hooks, ...]
[NotifyService, <Module>, BaseService, Hooks, ...]
etc
Is there a way I can accomplish this?
Thank you!
You don't modify child classes, and you don't call super from their execute methods.
As far as I can tell, there's no reason why CreateUserService#execute should call the wrapped BaseService#execute.
One way to achieve what you want are refinements:
class BaseService
def execute
p "BaseService#execute"
end
end
class CreateUserService < BaseService
def execute
p "CreateUserService#execute"
end
end
class NotifyService < BaseService
def execute
p "NotifyService#execute"
end
end
module WrappedExecute
[NotifyService, CreateUserService, BaseService].each do |service_class|
refine service_class do
def execute
puts "Before"
super
puts "After"
end
end
end
end
CreateUserService.new.execute
#=> "CreateUserService#execute"
using WrappedExecute
CreateUserService.new.execute
# Before
# "CreateUserService#execute"
# After
Note:
to_prepend = build_module(symbols) do |*args, &mblock|
result = nil
block.call do
result = super(*args, &mblock)
end
result
end
could be replaced by
to_prepend = build_module(symbols) do |*args, &mblock|
block.call do
super(*args, &mblock)
end
end
You'd still need to include Hooks in every Service class, though.
What I've ended up doing is something that I'm unsure whether I'm ok with or not.
class BaseService
include Hooks
def self.inherited(subclass)
subclass.around(:execute) do |&block|
Rails.logger.tagged(tags) do
block.call
end
end
end
end
This enabled me to apply to all other classes the around functionality to avoid a massive refactor. Not sure if this is the best solution but wanted to post for others to see.
In Rails we can define a class like:
class Test < ActiveRecord::Base
before_initialize :method
end
and when calling Test.new, method() will be called on the instance. I'm trying to learn more about Ruby and class methods like this, but I'm having trouble trying to implement this in plain Ruby.
Here's what I have so far:
class LameAR
def self.before_initialize(*args, &block)
# somehow store the symbols or block to be called on init
end
def new(*args)
## Call methods/blocks here
super(*args)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
I'm trying to figure out where to store the blocks in self.before_initialize. I originally tried an instance variable like #before_init_methods, but that instance variable wouldn't exist in memory at that point, so I couldn't store or retrieve from it. I'm not sure how/where could I store these blocks/procs/symbols during the class definition, to later be called inside of new.
How could I implement this? (Either having before_initialize take a block/proc/list of symbols, I don't mind at this point, just trying to understand the concept)
For a comprehensive description, you can always check the Rails source; it is itself implemented in 'plain Ruby', after all. (But it handles lots of edge cases, so it's not great for getting a quick overview.)
The quick version is:
module MyCallbacks
def self.included(klass)
klass.extend(ClassMethods) # we don't have ActiveSupport::Concern either
end
module ClassMethods
def initialize_callbacks
#callbacks ||= []
end
def before_initialize(&block)
initialize_callbacks << block
end
end
def initialize(*)
self.class.initialize_callbacks.each do |callback|
instance_eval(&callback)
end
super
end
end
class Tester
include MyCallbacks
before_initialize { puts "hello world" }
end
Tester.new
Left to the reader:
arguments
calling methods by name
inheritance
callbacks aborting a call and supplying the return value
"around" callbacks that wrap the original invocation
conditional callbacks (:if / :unless)
subclasses selectively overriding/skipping callbacks
inserting new callbacks elsewhere in the sequence
... but eliding all of those is what [hopefully] makes this implementation more approachable.
One way would be by overriding Class#new:
class LameAR
def self.before_initialize(*symbols_or_callables, &block)
#before_init_methods ||= []
#before_init_methods.concat(symbols_or_callables)
#before_init_methods << block if block
nil
end
def self.new(*args, &block)
obj = allocate
#before_init_methods.each do |symbol_or_callable|
if symbol_or_callable.is_a?(Symbol)
obj.public_send(symbol_or_callable)
else
symbol_or_callable.(obj)
end
end
obj.__send__(:initialize, *args, &block)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
I have ruby application and I want to implement a DSL to define Factories. Factories are classes that instantiates some object, execute some logic in the process, perform some validations and execute some callbacks depending on the results (succeeded or failed):
f = Factory.new
f.create(foo: :bar) do |on|
on.success { puts 'hey from success action callback' }
on.failure { puts 'hey from failure action callback' }
end
Ok, this is not that hard to do, but I also want to stop the create method right after the method fails or succeeds, something like:
def create(options = {})
# some logic
failed! # this method stops execution and yields the callback object
puts "you'll never see this"
end
What I came up with is this: https://gist.github.com/esdras/631a04769f24856c6d7f
See a partial version below:
require 'fiber'
class Factory
class Callbacks
# omitted some code here, this class is basically a container for
# success and failure callbacks
end
def failed!
#callbacks.failed!
resume_context
end
def succeeded!
#callbacks.succeeded!
resume_context
end
def resume_context ; Fiber.yield ; end
def self.handle(name, &method_body)
define_method "__original_#{name}__", &method_body
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { send("__original_#{name}__", *args) }.resume
#callbacks
end
end
handle :create do |options = {}|
puts options.inspect
puts "in create"
succeeded!
puts 'after succeeded, never reached here'
end
end
As you can see the class method handle defines two methods: __original_create__ and create which wraps __original_create__ in a Fiber to make it possible to stop execution immediately and execute the callbacks. My question is: Is there a better way to do this? Without creating that __original_create__ method or even without using Fibers?
I already tried this:
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { method_body.call *args }.resume
# above method_body is evaluated in the context of the class.
#callbacks
end
end
but method_body is evaluated in the context of the class, not the instance:
I also tried to instance_eval the method_body like this:
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { instance_eval &method_body }.resume
# above we lost the parameters defined by the handle method
#callbacks
end
end
but I lost the reference to the parameters defined by:
handle :create do |param1, param2|
# method body
end
The only way I found is defining a method with the block passed to the handle method and after defining a wrapper method that calls the original method, like I did above with __original_create__. I'm not ok with defining an extra method, there got to be another way to do this. :(
Any insights would be appreciated.
I'm not sure what you need the Fiber for, so I will leave it, but you need is instance_exec
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { instance_exec *args, &method_body }.resume
# above we lost the parameters defined by the handle method
#callbacks
end
end
One way to do it is to use throw and catch.
def create(options = {})
case catch(:result) do
throw :result, :success if ...
throw :result, :error if ...
end
when :success then ...
when :error then ...
end
end
I would like:
module MyLog
def log
unless #log
#log = Logger.new(log_path)
#log.formatter = proc do |severity, datetime, progname, msg|
"#{datetime} #{msg}\n"
end
end
#log
end
end
To be reused between other classes like this:
Class A
def self.log_path; 'log/a.log' end
def log_path; 'log/a.log' end
include MyLog
extend MyLog
def some_method
log.debug 'some thing'
end
def self.some_class_method
log.debug 'in a class method'
end
end
Is there a shorter way than those four lines at start of class A?
Another thing
I would like to log by batches:
def expire
expired_ids = []
failed_ids = []
all.each do |event|
if event.expire # saves record
expired_ids << event.id
else
failed_ids << event.id
end
end
log.debug "These ids were expired: #{ expired_ids }"
log.debug "These ids failed to expire: #{ failed_ids }"
end
Is there a way I can do this cleanly? Separating logging from method logic?
This is what I've been doing recentrly when faced with problems like yours:
class A
class << self
include MyLog
def log_path
'log/a.log'
end
end
delegate :log_path, :log, to: "self.class"
end
Otherwise, choosing the best programing approach depends on your situation, on how often you are going to reuse the script, how many times will you have to refactor it during its lifetime, and so on. But in any case, please, don't try to save the lines of code, try to save the trouble to yourself when you are reading the code next time after yourself.
As for your second question, the main dilemma you are facing is, whether it is worth bother for you to introduce Events class:
class Events < Array
def foobar
each_with_object [[], []] do |event, (expired_ids, failed_ids)|
( event.expire ? expired_ids : failed_ids ) << event.id
end
end
end
And then when the time comes:
def expire
expired_ids, failed_ids = Events[ all ].foobar
log.debug "These ids were expired: #{ expired_ids }"
log.debug "These ids failed to expire: #{ failed_ids }"
end
You can use the included hook to automatically define class methods and instance methods:
module MyLog
def self.included(base)
base.extend(Methods)
base.send(:include, Methods)
end
module Methods
def log_path; 'log/a.log' end
def log
unless #log
#log = Logger.new(log_path)
#log.formatter = proc do |severity, datetime, progname, msg|
"#{datetime} #{msg}\n"
end
end
#log
end
end
end
Like this, both class and instance methods will be automatically defined once the module gets included.
For your second problem, use partition and return the values, then log them:
def expire
all.partition(&:expire)
end
Then, where you call expire, you can log the return values:
def call_something
expired, failed = expire
log.debug "These ids were expired: #{expired.map(&:id)}"
log.debug "These ids failed to expire: #{failed.map(&:id)}"
end
If your log path is always supposed to be log/<lower_case_class>.log, you can implement that in your module and you should be fine. When the module's methods are executed, self will still be the object the method was called on, so you can say self.class and get A not your module name.
Check out the accepted answer on this question for how to add methods to both your class as well as your objects of that class
Why 'self' method of module cannot become a singleton method of class?
You can use Enumerable#partition to break up your "all" (you didn't actually say where that came from in your code you posted)
expired, failed = all.partition(&:expire)
My first thoughts are some thing like this:
class AbstractBuilder
attr_reader :time_taken
def build_with_timer
started_at = Time.now
build
#time_taken = Time.now - started_at
end
def build
raise 'Implement this method in a subclass'
end
end
class MyBuilder < AbstractBuilder
def build
sleep(5)
end
end
builder = MyBuilder.new.build_with_timer
puts builder.time_taken
I would suspect there is a better way which offers better flexibility, for example ideally I'd like to call 'build' on an instance of MyBuilder instead of 'build_with_timer' and always have the execution time recorded.
I did consider using alias_method from initialize or even using a module mixin instead of class inheritance which would override the build method calling super in the middle (not sure if that would work). Before I go down the rabbit hole I thought I'd see if there is an established practice.
I had a stab at a version to achieve what you want. This version doesn't require the subclass to have any extra code either.
class AbstractBuilder
##disable_override = false
def before_method
puts "before"
end
def after_method
puts "after"
end
def self.method_added name
unless ##disable_override
if name == :build
##disable_override = true # to stop the new build method
self.send :alias_method, :sub_build, :build
self.send :remove_method, :build
self.send :define_method, :build do
before_method
sub_build
after_method
end
##disable_override = false
else
puts "defining other method #{name}"
end
end
end
end
class MyBuilder < AbstractBuilder
def build
puts "starting build"
sleep(5)
puts "built."
end
def unnaffected_method
# this method won't get redefined
end
end
b = MyBuilder.new
b.build
Outputs
defining other method unnaffected_method
before
starting build
built.
after
I'd play with alias_method:
module Timeable
def time_methods *meths
meths.each do |meth|
alias_method "old_#{meth}", meth
define_method meth do |*args|
started_at = Time.now
res = send "old_#{meth}", *args
puts "Execution took %f seconds" % (Time.now - started_at)
res
end
end
end
end
class Foo
def bar str
puts str
end
end
Foo.extend Timeable
Foo.time_methods :bar
Foo.new.bar('asd')
#=>asd
#=>Execution took 0.000050 seconds
Sounds like you're looking for hooks into object lifecycle events. You'll have to build this into your base object and provide a little DSL -- I'm thinking you're after something like ActiveRecord Callbacks. Here's how we might modify your example to allow something like that:
class AbstractBuilder
attr_reader :time_taken
def construct! # i.e., build, and also call your hooks
##prebuild.each { |sym| self.send(sym) }
build
##postbuild.each { |sym| self.send(sym) }
end
def construct_with_timer
started_at = Time.now
construct!
#time_taken = Time.now - started_at
puts "!!! Build time: ##time_taken"
end
class << self
def before_build(fn); ##prebuild ||= []; ##prebuild << fn; end
def after_build(fn); ##postbuild ||= []; ##postbuild << fn; end
end
end
class MyBuilder < AbstractBuilder
before_build :preprocess
after_build :postprocess
def build; puts "BUILDING"; sleep(3); end
def preprocess; puts "Preparing to build..."; end
def postprocess; puts "Done building. Thank you for waiting."; end
end
builder = MyBuilder.new
builder.construct_with_timer
# => Preparing to build...
# => BUILDING
# => Done building. Thank you for waiting.
# => !!! Build time: 3.000119
This is a textbook-definition use case for Aspect-Oriented Programming. It generally offers a cleaner separation of concerns. In this arena, Ruby offers Aquarium and AspectR. However, you may not want to add another dependency to your project. As such, you might still consider using one of the other approaches.