I'm writing specs for a gem of mine that extends ActiveRecord. One of the things it has to do is set a class instance variable like so:
class MyModel < ActiveRecord::Base
#foo = "asd"
end
Right now when I set #foo in one it "should" {} it persists to the next one. I understand this is normal Ruby behavior but I thought RSpec had some magic that cleaned everything out in between specs. I'd like to know how I can re-use a single AR model for all my tests (since creating a bunch of tables would be a pain) while being sure that #foo is being cleared between each test. Do I need to do this manually?
I wound up generating a method in my helper class that generated new classes with Class.new, so I could be sure that nothing was being left over in between tests.
You should simply make good use of the after :each block.
after(:each) do
#foo = nil
end
Related
I've got a class in a namespace with a few methods
module Foo
module Bar
class Baz
def initialize(arg1, arg2, arg3)
# do stuff
end
def delete
File.delete(#path)
end
end
end
end
In my test environment, I don't want delete to delete any files, so in a TestHelper, I do this
class Foo::Bar::Baz
def delete
puts "no delete in test"
end
end
When I initialize this class this in my test, I get ArgumentError: wrong number of arguments (3 for 0). That is, the initialize method of Baz is gone. And to be sure, if I take a look at self in my test helper, there are no methods defined at all for Baz. It's been completely overridden by the class keyword.
I can make it work by using class_eval instead of class, i,e.
Foo::Bar::Baz.class_eval do
def delete
# etc
end
end
My question is, what is the difference? Why does the latter work but not the former?
I could be wrong, but I think you're accidentally breaking the autoloader. Here's what I think is happening in your working case (using .class_eval):
Something, somewhere, loads code that defines Foo::Bar (you'd be getting other errors if this wasn't happening)
Test code is parsed; explicitly requires TestHelper
TestHelper references Foo::Bar::Baz, which does not exist
autoloader finds and loads foo/bar/baz.rb
TestHelper runs class_eval and redefines #delete
Test code runs
And here's my guess at the non-working case:
Again, something, somewhere, loads code that defines Foo::Bar
Test code is parsed; explicitly requires TestHelper
TestHelper creates Foo::Bar::Baz, since it didn't already exist
Test code runs
Notice in the second case the autoloader was never triggered, so your actual class definition is never loaded.
I'm not sure the best way to solve this. You could do an explicit require in your test, or just reference the class in your helper before redefining it:
Foo::Bar::Baz # trigger autoloading before we muck with the definition
class Foo::Bar::Baz
def delete
puts "no delete in test"
end
end
In console:
class Logger
end
l = Logger.new
Throws error:ArgumentError: wrong number of arguments (0 for 1)
from /home/zzz/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/logger.rb:268:in 'initialize'
Why is it using the Logger in /home/zzz/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/logger.rb?
I'll try to answer to your second question. So, why is Ruby using some other Logger class and not the one you tried to define yourself?
One of the fundamental features of Ruby is re-opening of classes. Let's say you have some class that is already defined and loaded in your app:
class A
def foo
puts 'foo'
end
end
A.new.foo
#=> foo
If after that Ruby interpreter encounters something like:
class A
def bar
puts 'bar'
end
end
it doesn't redefine class A, but simply appends this definition to the previous one. In the result already defined class A gets new instance method bar:
A.new.foo # still works
#=> foo
A.new.bar # new method
#=> bar
Because of the way Ruby handles methods calling, all instances of class A that were initialized before the second definition (actually, re-opening) of class A also get this new method bar. So every time you reopen a class you add new functionality to the class itself and to all previously initialized instances of this class.
Classes reopening also allows rewriting methods of an existing class:
class A
def foo
puts 'new foo'
end
end
A.new.foo
#=> new_foo
With that feature in mind and the fact that Rails has already loaded standard Logger class for you, your definition only reopens the class, but doesn't even change anything.
That class was already loaded, presumably because rails was using it: you haven't redefined the class, you were merely re-opening it.
You could remove the existing class
Object.send(:remove_const, :Logger)
In this case the class formerly known as Logger still exists, it just isn't bound to the constant Logger any more, so when you do
class Logger
end
you'll be creating a new class not reopening the old one. You may of course end up breaking code that assumed the presence of the old Logger class.
If you're doing this in tests, you may be interested in the new constant stubbing in rspec 2.11
Along the same lines as this question, I want to call acts_as_reportable inside every model so I can do one-off manual reports in the console in my dev environment (with a dump of the production data).
What's the best way to do this? Putting acts_as_reportable if ENV['RAILS_ENV'] == "development" in every model is getting tedious and isn't very DRY at all. Everyone says monkey patching is the devil, but a mixin seems overkill.
Thanks!
For me the best way will be to add it into the ActiveRecord::Base in the initializer. I believe the acts_as_reportable is a mixin under the hood. By doing this, when you will be able to call all the method that came with acts_as_reportable in all your models in development environment only.
I will do it in the config/initializers directory, in a file called model_mixin.rb or anything that you wish.
class ActiveRecord::Base
acts_as_reportable if (ENV['RAILS_ENV'] == "development")
end
The argument of using monkey patch is dirty depends on yourself and how readable the code is, in my opinion, use what you are comfortable with. The feature are there to be used and it always depends on the user.
What about creating a Reportable class and deriving all the models from it?
class Reportable
acts_as_reportable if ENV['RAILS_ENV'] == "development"
end
class MyModel < Reportable
end
I use a mixin for common methods across all my models:
module ModelMixins
# Splits a comma separated list of categories and associates them
def process_new_categories(new_categories)
unless new_categories.nil?
for title in new_categories.split(",")
self.categories << Category.find_or_create_by_title(title.strip.capitalize)
end
self.update_counter_caches
end
end
end
I considered doing it in other ways, but to me this seems to be the most legitimate way of DRYing up your models. A model equivalent of the ApplicationController would be a neat solution, though I'm not sure how you'd go about that, or whether there's a decent argument against having one.
I'm using RR for mocking and stubbing in RSpec, and I've run across a situation where I'd like to stub a method from a super class of a controller that sets some instance variables. I can work out how to stub the method call and if I debug I can see that my stubbed block is called, but I cannot get the instance variables in the block to propagate into the class I'm testing.
Just to break it down :
class A < ApplicationController
before_filter :bogglesnap
def bogglesnap
#instancevar = "totally boggled"
end
end
class B < A
def do_something_with_instance
if #instancevar
....
else
....
end
end
end
That's the basic setup, and so then in my tests for controller B I'd like to stub out the bogglesnap method from A to set #instancevar to something I want. I just can't figure out how to do it.
I've tried RR's instance_of stubbing and just stubbing out the controller definition :
stub.instance_of(A).bogglensap { #instancevar = "known value" }
stub(controller).bogglesnap { #instancevar = "known value" }
but neither of these seem to work, well, they don't work :)
Does anyone have any pointers on how you should be able to stub that method call out and have it set instance variables? I'm assuming it has to do with the context in which the block is run but am hoping someone has run across something like this before.
Thanks
You can use instance_variable_set method by calling on the object instance and set it to whatever you want, like so
controller.instance_variable_set("#instancevar", "known value")
and similarly, if you ever want to fetch the value of an instance variable in your spec or debug or do something else from outside the class then you can get the value by doing
controller.instance_variable_get("#instancevar")
Mind you, instance_variable_set and instance_variable_get methods are available not only to controllers but all objects as it is provided by ruby. Infact, these two methods play an important role in rails magic :)
Ok, so I've been refactoring my code in my little Rails app in an effort to remove duplication, and in general make my life easier (as I like an easy life). Part of this refactoring, has been to move code that's common to two of my models to a module that I can include where I need it.
So far, so good. Looks like it's going to work out, but I've just hit a problem that I'm not sure how to get around. The module (which I've called sendable), is just going to be the code that handles faxing, e-mailing, or printing a PDF of the document. So, for example, I have a purchase order, and I have Internal Sales Orders (imaginatively abbreviated to ISO).
The problem I've struck, is that I want some variables initialised (initialized for people who don't spell correctly :P ) after the object is loaded, so I've been using the after_initialize hook. No problem... until I start adding some more mixins.
The problem I have, is that I can have an after_initialize in any one of my mixins, so I need to include a super call at the start to make sure the other mixin after_initialize calls get called. Which is great, until I end up calling super and I get an error because there is no super to call.
Here's a little example, in case I haven't been confusing enough:
class Iso < ActiveRecord::Base
include Shared::TracksSerialNumberExtension
include Shared::OrderLines
extend Shared::Filtered
include Sendable::Model
validates_presence_of :customer
validates_associated :lines
owned_by :customer
order_lines :despatched # Mixin
tracks_serial_numbers :items # Mixin
sendable :customer # Mixin
attr_accessor :address
def initialize( params = nil )
super
self.created_at ||= Time.now.to_date
end
end
So, if each one of the mixins have an after_initialize call, with a super call, how can I stop that last super call from raising the error? How can I test that the super method exists before I call it?
You can use this:
super if defined?(super)
Here is an example:
class A
end
class B < A
def t
super if defined?(super)
puts "Hi from B"
end
end
B.new.t
Have you tried alias_method_chain? You can basically chained up all your after_initialize calls. It acts like a decorator: each new method adds a new layer of functionality and passes the control onto the "overridden" method to do the rest.
The including class (the thing that inherits from ActiveRecord::Base, which, in this case is Iso) could define its own after_initialize, so any solution other than alias_method_chain (or other aliasing that saves the original) risks overwriting code. #Orion Edwards' solution is the best I can come up with. There are others, but they are far more hackish.
alias_method_chain also has the benefit of creating named versions of the after_initialize method, meaning you can customize the call order in those rare cases that it matters. Otherwise, you're at the mercy of whatever order the including class includes the mixins.
later:
I've posted a question to the ruby-on-rails-core mailing list about creating default empty implementations of all callbacks. The saving process checks for them all anyway, so I don't see why they shouldn't be there. The only downside is creating extra empty stack frames, but that's pretty cheap on every known implementation.
You can just throw a quick conditional in there:
super if respond_to?('super')
and you should be fine - no adding useless methods; nice and clean.
Rather than checking if the super method exists, you can just define it
class ActiveRecord::Base
def after_initialize
end
end
This works in my testing, and shouldn't break any of your existing code, because all your other classes which define it will just be silently overriding this method anyway