0

I have a Project class with two ObjectSpace-related methods:

def self.all
  ObjectSpace.each_object(self).to_a
end

def self.count
  ObjectSpace.each_object(self).count
end

This spec is failing:

it "can print all projects" do
  Project.all.should eq([@project1, @project2])
end

with the following error:

Failure/Error: Project.all.should eq([@project1, @project2])

       expected: [#<Project:0x007fd76a815508 @name="Building house", @tasks=[]>, #<Project:0x007fd76a815198 @name="Getting a loan from the Bank", @tasks=[]>]
            got: [#<Project:0x007fd7688336b8 @name="Building house", @tasks=[]>, #<Project:0x007fd7688dae40 @name="Building house", @tasks=[]>, #<Project:0x007fd768af4de8 @name="Getting a loan from the Bank", @tasks=[]>, #<Project:0x007fd768af5090 @name="Building house", @tasks=[]>, #<Project:0x007fd76a815198 @name="Getting a loan from the Bank", @tasks=[]>, #<Project:0x007fd76a815508 @name="Building house", @tasks=[]>]

As you can see, this is giving me my objects in an array doubled but the code itself works fine. So why is my test failing?

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
Amit Erandole
  • 11,995
  • 23
  • 65
  • 103
  • Man, why is this question voted down just because it had to be edited for brevity? – Amit Erandole Jan 14 '13 at 15:36
  • 1
    Your question, as originally posted, was a "wall of code" that would likely have been closed as "Too Localized." Asking good questions requires boiling your issue down to its essentials, and ensuring that the question is also relevant to future visitors by generalizing it as much as possible, rather than asking the Internet to help you debug your code. Please see http://stackoverflow.com/questions/how-to-ask for more detail. There was a good question about ObjectSpace buried there; hopefully the edits have brought it to the surface. – Todd A. Jacobs Jan 14 '13 at 15:53
  • Ok I guess. But that could have just been pointed out to me and I would have made the edits myself – Amit Erandole Jan 14 '13 at 16:34

3 Answers3

2

Because previously-existing Projects still exist as objects.

This means they'll still be found in ObjectSpace, and you'll have more objects than you expect.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
  • aah - you are right. So what is the right way to fix this? Should I destroy after the context? How do I do that? – Amit Erandole Jan 14 '13 at 15:14
  • 1
    @AmitErandole I'm not sure; you could start with [this SO answer](http://stackoverflow.com/questions/9406419/deleting-an-object-in-ruby). Alternatively you could stick with your original solution and keep a list of references, but remove them at the end of each spec. Not sure what the best idea is, though, sorry. – Dave Newton Jan 14 '13 at 15:18
  • Hmm so I guess using ObjectSpace is a no go - Is that really the lesson to be learnt here? I wonder... – Amit Erandole Jan 14 '13 at 15:19
  • 1
    @AmitErandole - You can define a `finalizer` on your `Project` class which will be called when you do `ObjectSpace.garbage_collect`. But the implementation would really be driven by the fact - Do you need just your specs to pass or implementation to be strict about context? – Jasdeep Singh Jan 14 '13 at 15:23
  • I need both I guess. But what I am wondering about right now is whether I should be using `ObjectSpace` in the first place. Let me see how I can use a finalizer – Amit Erandole Jan 14 '13 at 15:25
  • @JasdeepSingh I tried adding `ObjectSpace.define_finalizer(self, self.class.finalize(self))` but now all my tests fail. Maybe I did it wrong?! No experience with using finalizers. Please help... – Amit Erandole Jan 14 '13 at 15:34
  • @AmitErandole is there any reason why you cannot use a custom method during initialize that keeps track of all objects? Posting an answer soon. – Jasdeep Singh Jan 14 '13 at 16:12
  • @JasdeepSingh His original code had such code commented out (I removed it because it wasn't directly related). That's why I suggested reverting to his original idea in my first comment. – Dave Newton Jan 14 '13 at 16:14
  • I can use a custom method I guess but I wanted to understand how this ObjectSpace api works. – Amit Erandole Jan 14 '13 at 16:33
1

ObjectSpace May Contains Traces of Nuts

Well, not really. But ObjectSpace often contains objects that belong to other scopes, objects which have been marked for garbage collection but not yet removed, or (in the case of RSpec tests in particular) copies of objects from multiple invocations of a before block.

Ruby 2.0 may be different, but earlier MRI interpreters don't make guarantees about garbage collection, so you can't really count on the contents of ObjectSpace to be valid for an equality test even if you manually run GC.start.

Refactor Your Code

Instead of looking for equality in your spec, you might consider:

  1. Looking for inclusion with Array#include?
  2. Looking for specific object IDs within ObjectSpace.
  3. Refactoring the class under test, as well as the test itself. Specifically, use an aggregation pattern of some sort rather than relying on ObjectSpace to hold references to the Project objects you care about.

You can mix-and-match here, but fixing your test is really only part of the problem. The underlying application logic seems to be in need of refactoring.

The failing test is good, in that it highlights a class that needs surgery. Don't just fix the test; listen to what the spec is trying to tell you about the class under test.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
1

project_spec.rb

describe Project do

 let(:p1) { Project.new }
 let(:p2) { Project.new }

 describe ".all" do

  it "should keep track of all pr" do
   Project.all.should == [p1, p2]  
  end

 end

 describe ".count" do

  it "should count all the projects" do
   Project.count.should == 2
  end

 end

end

project.rb

class Project

  @@all_projects = []

  def initialize(options=nil)
    @@all_projects << self
  end

  def self.all
    @@all_projects
  end

  def self.count
    @@all_projects.count
  end

end


Finished in 0.00079 seconds
2 examples, 0 failures

I'll leave it upto you to work out the other details that might be specific and intricate to your project. Hope this works out for you.

Jasdeep Singh
  • 3,276
  • 4
  • 28
  • 47
  • This is nice and it works but if I wanted to delete all objects, would I then be stuck again with garbage collection issues? Or would I just empty out that array and that would just work out..? – Amit Erandole Jan 14 '13 at 16:53
  • Once again, these are all learning exercises for me - I guess now I am trying to understand the lifecycle of a ruby object through its basic CRUD cycle. – Amit Erandole Jan 14 '13 at 16:54
  • Why would you want to manually clean up the objects? Just curious? Emptying out the Array will just clear the array. The objects would still be there in the memory – Jasdeep Singh Jan 14 '13 at 17:03
  • Oh I see so what you are saying is using `ObjectSpace` locks you into a decision to manually manage the clean up of objects? Whereas with the array I wouldn't have to worry about these things – Amit Erandole Jan 14 '13 at 17:23
  • 1
    That is about right. :) If this answer solved your query, I'll appreciate if you can give it some green love. :) – Jasdeep Singh Jan 14 '13 at 18:52