Meta Programming and Unit Testing in ruby - ruby-on-rails

I am using rspec to do my testing and while I am not looking for full blown answer, more of a pseudo code approach to get me going - I am unsure how to write tests for the following class.
module AisisWriter
class ClassFactory
class << self
undef_method :new
attr_accessor :registered_objects
def register(class_name, klass, params = nil)
if !params.is_a(Array)
raise ArgumentError, "params must be an array"
end
registered_object[class_name] = {:class_name => klass, :params => !params.nil? ? params.flatten : nil}
end
def create(class_name, params = nil)
if !params.is_a(Array)
raise ArgumentError, "params must be an array"
end
klass = registered_object[class_name]
if !params.nil
klass[:class_name].new(params.flatten)
else
flass[:class_name].new(*klass[:params])
end
end
end
end
end
I write this class in rails to allow me to register frequently used classes and be able to quickly create them on the fly. This concept comes from my question on reflection in ruby.
What I am unsure of how to test is two things. One I have to make sure the class you register is actually registered. Two that if you try and create an instance of the class that its actually created.
I could write a get_registered_objects function to help with the first one and compare it to an existing object or traverse it looking for specific attributes.
But the second part, how do you test that a class was instantiated?

In order to test .register in isolation, you would need a getter or maybe a .registered? method which would allow you to access the internals of your singleton class.
You could also test .register by registering and then creating a class:
describe AisisWriter::ClassFactory do
describe '.create' do
let(:klass) { Hash.new }
before { AisisWriter::ClassFactory.register('Foo', ClassStub, []) }
it "registers the class" do
instance = AisisWriter::ClassFactory.create('Foo', [])
expect(instance).to be_a
end
end
end
Apart from that your code is littered with issues - you may want to read up on how class variables work - and what attr_accessor does when placed in class eval.
Also you could reduce the arity and complexity of register by using:
def register(klass, params = nil)
class_name = klass.name
end
Ruby uses ? at the end of interrogative methods is_a?, nil?.

Related

Ruby/Rails testing - access variable outside of scope?

I want to unit test a method with rspec for RoR and have a method like this:
def create_record(obj, params)
begin
obj.add_attributes(params)
result = obj.save
rescue
MyMailer.failed_upload(#other_var, obj.api_class_name, params).deliver_now
end
end
create_record is never invoked directly, but through another method which fills in #other_var appropriately.
How should I go about testing the code to make sure MyMailer is called correctly? Should I have passed #other_var into the method instead of relying on it being filled in elsewhere (aka: is this a code smell?)? Thanks!
In Ruby you can use Object#instance_variable_set to set any instance variable.
RSpec.describe Thing do
describe "#create_record" do
let(:thing) do
t = Thing.new
t.instance_variable_set(:#other_var, "foo")
t
end
# ...
end
end
This completely circumvents any encapsulation which means that the use of instance_variable_set can be considered a code smell.
Another alternative is to use RSpecs mocking and stubbing facilities but stubbing the actual object under test is also a code smell.
You can avoid this by passing the dependency as a parameter or by constructor injection:
class Thing
attr_accessor :other_var
def initialize(other_var: nil)
#other_var = other_var
end
def create_record(obj, attributes)
# ...
end
end
A good pattern for this is service objects.

Dynamically defining instance method within an instance method

I have a several classes, each of which define various statistics.
class MonthlyStat
attr_accessor :cost, :size_in_meters
end
class DailyStat
attr_accessor :cost, :weight
end
I want to create a decorator/presenter for a collection of these objects, that lets me easily access aggregate information about each collection, for example:
class YearDecorator
attr_accessor :objs
def self.[]= *objs
new objs
end
def initialize objs
self.objs = objs
define_helpers
end
def define_helpers
if o=objs.first # assume all objects are the same
o.instance_methods.each do |method_name|
# sums :cost, :size_in_meters, :weight etc
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
end
end
end
YearDecorator[mstat1, mstat2].yearly_cost_sum
Unfortunately define method isn't available from within an instance method.
Replacing this with:
class << self
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
...also fails because the variables method_name and objs which are defined in the instance are no longer available. Is there an idomatic was to accomplish this in ruby?
(EDITED: I get what you're trying to do now.)
Well, I tried the same approaches that you probably did, but ended up having to use eval
class Foo
METHOD_NAMES = [:foo]
def def_foo
METHOD_NAMES.each { |method_name|
eval <<-EOF
def self.#{method_name}
\"#{method_name}\".capitalize
end
EOF
}
end
end
foo=Foo.new
foo.def_foo
p foo.foo # => "Foo"
f2 = Foo.new
p f2.foo # => "undefined method 'foo'..."
I myself will admit it's not the most elegant solution (may not even be the most idiomatic) but I've run into similar situations in the past where the most blunt approach that worked was eval.
I'm curious what you're getting for o.instance_methods. This is a class-level method and isn't generally available on instances of objects, which from what I can tell, is what you're dealing with here.
Anyway, you probably are looking for method_missing, which will define the method dynamically the first time you call it, and will let you send :define_method to the object's class. You don't need to redefine the same instance methods every time you instantiate a new object, so method_missing will allow you to alter the class at runtime only if the called method hasn't already been defined.
Since you're expecting the name of a method from your other classes surrounded by some pattern (i.e., yearly_base_sum would correspond to a base method), I'd recommend writing a method that returns a matching pattern if it finds one. Note: this would NOT involve making a list of methods on the other class - you should still rely on the built-in NoMethodError for cases when one of your objects doesn't know how to respond to message you send it. This keeps your API a bit more flexible, and would be useful in cases where your stats classes might also be modified at runtime.
def method_missing(name, *args, &block)
method_name = matching_method_name(name)
if method_name
self.class.send :define_method, name do |*args|
objs.inject(0) {|obj, sum| sum + obj.send(method_name)}
end
send name, *args, &block
else
super(name, *args, &block)
end
end
def matching_method_name(name)
# ... this part's up to you
end

Understanding Avdi's objects on rails code

I'm reading through Avdi's objects on rails book and don't understand a section of sample code.
He creates a class like so I guess for dependency injection purposes:
class Blog
# ...
attr_writer :post_source
# ...
private
def post_source
#post_source ||= Post.public_method(:new)
end
end
Then he writes the following spec
# spec/models/blog_spec.rb
require 'ostruct'
describe Blog do
# ...
describe "#new_post" do
before do
#new_post = OpenStruct.new
#it.post_source = ->{ #new_post }
end
it "returns a new post" do
#it.new_post.must_equal #new_post
end
it "sets the post's blog reference to itself" do
#it.new_post.blog.must_equal(#it)
end
end
end
I don't understand why he uses #it.post_source = ->{ #new_post }
Why didn't he just use something like #it.post_source = OpenStruct.public_method(:new) which would be similar to his Blog class code which has #post_source ||= Post.public_method(:new)
Is there a reason for this?
->{ #new_post } is a lambda that returns the instance stored in #new_post.
Post.public_method(:new) would return the constructor method of Post
Passing in the lambda for the class to use lets you have control of the instance that is returned. Passing in a class' constructor means you don't know what instance it will get, just that it will be of the class you specified.
It provides a reference for the it spec, otherwise it couldn't be compared in the it "returns a new post" block. It uses the simple dependency injection mechanism allowed in the post_source method to ease testability.

Mocking/stubbing a method that's included from "instance.extend(DecoratorModule)"

I use a decorator module that get's included in a model instance (through the "extends" method). So for example :
module Decorator
def foo
end
end
class Model < ActiveRecord::Base
end
class ModelsController < ApplicationController
def bar
#model = Model.find(params[:id])
#model.extend(Decorator)
#model.foo
end
end
Then I would like in the tests to do the following (using Mocha) :
test "bar" do
Model.any_instance.expects(:foo).returns("bar")
get :bar
end
Is this possible somehow, or do you have in mind any other way to get this functionality???
Just an Assumption Note: I will assume that your Decorator foo method returns "bar" which is not shown in the code that you sent. If I do not assume this, then expectations will fail anyway because the method returns nil and not "bar".
Assuming as above, I have tried the whole story as you have it with a bare brand new rails application and I have realized that this cannot be done. This is because the method 'foo' is not attached to class Model when the expects method is called in your test.
I came to this conclusion trying to follow the stack of called methods while in expects. expects calls stubs in Mocha::Central, which calls stubs in Mocha::ClassMethod, which calls *hide_original_method* in Mocha::AnyInstanceMethod. There, *hide_original_method* does not find any method to hide and does nothing. Then Model.foo method is not aliased to the stubbed mocha method, that should be called to implement your mocha expectation, but the actual Model.foo method is called, the one that you dynamically attach to your Model instance inside your controller.
My answer is that it is not possible to do it.
It works (confirmed in a test application with render :text)
I usually include decorators (instead of extending them at runtime) and I avoid any_instance since it's considered bad practice (I mock find instead).
module Decorators
module Test
def foo
"foo"
end
end
end
class MoufesController < ApplicationController
def bar
#moufa = Moufa.first
#moufa.extend(Decorators::Test)
render :text => #moufa.foo
end
end
require 'test_helper'
class MoufesControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "bar" do
m = Moufa.first
Moufa.expects(:find).returns(m)
m.expects(:foo).returns("foobar")
get :bar, {:id => 32}
assert_equal #response.body, "foobar"
end
end
Ok, now I understand. You want to stub out a call to an external service. Interesting that mocha doesn't work with extend this way. Besides what is mentioned above, it seems to be because the stubbed methods are defined on the singleton class, not the module, so don't get mixed in.
Why not something like this?
test "bar" do
Decorator = Module.new{ def foo; 'foo'; end }
get :bar
end
If you'd rather not get the warnings about Decorator already being defined -- which is a hint that there's some coupling going on anyway -- you can inject it:
class ModelsController < ApplicationController
class << self
attr_writer :decorator_class
def decorator_class; #decorator_class ||= Decorator; end
end
def bar
#model = Model.find(params[:id])
#model.extend(self.class.decorator_class)
#model.foo
end
end
which makes the test like:
test "bar" do
dummy = Module.new{ def foo; 'foo'; end }
ModelsController.decorator_class = dummy
get :bar
end
Of course, if you have a more complex situation, with multiple decorators, or decorators defining multiple methods, this may not work for you.
But I think it is better than stubbing the find. You generally don't want to stub your models in an integration test.
One minor change if you want to test the return value of :bar -
test "bar" do
Model.any_instance.expects(:foo).returns("bar")
assert_equal "bar", get(:bar)
end
But if you are just testing that a model instance has the decorator method(s), do you really need to test for that? It seems like you are testing Object#extend in that case.
If you want to test the behavior of #model.foo, you don't need to do that in an integration test - that's the advantage of the decorator, you can then test it in isolation like
x = Object.new.extend(Decorator)
#.... assert something about x.foo ...
Mocking in integration tests is usually a code smell, in my experience.

Ruby on Rails: Passing argument to singleton

I have a Rails app that repeatedly talks to another Web server through a wrapper, and I'd like to stick the wrapper in a Singleton class so it's not recreated for every request. Easy enough, I thought:
class AppWrapper < Wrapper
include Singleton
end
...
wrapper = AppWrapper.instance "url"
Only it doesn't work:
wrong number of arguments (0 for 1)
/usr/lib/ruby/1.8/singleton.rb:94:in `initialize'
/usr/lib/ruby/1.8/singleton.rb:94:in `new'
/usr/lib/ruby/1.8/singleton.rb:94:in `instance'
Wrapper.initialize needs an argument, and apparently it's not getting passed through, since line 94 in question says
#__instance__ = new # look Ma, no argument
How do I work around this? Redefining initialize in AppWrapper doesn't seem to help, and
mucking around with Wrapper to separate "set URL" from "initialize" seems suboptimal.
Passing argument to singleton
class Parameterized_Singleton
def initialize(a)
#pdf = a
puts #pdf
end
def self.instance(p)
begin
##instance =Parameterized_Singleton.new(p)
private_class_method :new
rescue NoMethodError
# return ##instance # or you can return previous object
puts "Object Already Created"
exit
end
return ##instance
end
def scanwith(b)
puts "scan"
end
def show_frequence_distribution
puts "fd"
end
def show_object_number(a)
puts "no"
end
end
Parameterized_Singleton.instance(20).show_object_number(10)
Parameterized_Singleton.instance(10).show_object_number(20)
Are you sure you need a singleton and not a factory . Refer this
I asked this question while I was still getting my head around Ruby, and it seems so naive now. The easy solution is to just store the Wrapper object in a member variable and use ||= to initialize it only if it hasn't been set yet:
class WrapperUserClass
def initialize
#wrapper = nil # Strictly speaking unnecessary, but it's a bit clearer this way
end
def wrapper
#wrapper ||= Wrapper.new(foobar)
end
def do_something
wrapper.booyakasha
end
end
Since you mention something about editing Wrapper as a solution, can't you just use Wrapper directly and do this?
class Wrapper; include Singleton; end
If not, you could use something like this, which will just make sure AppWrapper.new isn't called more than once:
class AppWrapper
def self.new(*args)
class << app_wrapper = Wrapper.new(*args)
include Singleton
end
app_wrapper
end
end
If you need the singleton "Klass.instance" method, you'll have to take either take out the parameter in Wrapper#initialize, or just redefine Singleton#instance to take arguments optionally and passes them to the call to new on line 94.

Resources