Search code examples
ruby-on-railsrubyrspec-rails

How are these methods being added to my class?


My Writer model class does not have 'posted_posts' or 'rejected_posts' attributes on it. However, with this SQL:

posts = self.posts.select("sum(case when state = 'posted' then 1 else 0 end) as posted_posts, sum(case when state = 'rejected' then 1 else 0 end) as rejected_posts")

I can access those 'properties' on the Writer instance. When I using RSpec though, I don't want to have to make this query, but I am unable to set those properties of the Writer instance because it thinks I am calling a method instead:

writer.rejected_posts = 8 

The above line results in "undefined method 'rejected_posts'"

To 'mock' those properties, I did this:

describe "performance_score" do
    it "should return a score based on the posted_posts and rejected_posts algorithm" do
      class Writer
        attr_accessor :posted_posts, :rejected_posts
      end
      writer = Factory(:writer)
      writer.rejected_posts = 8
      writer.posted_posts = 2
      writer.performance_score.should == 80
    end
  end

My main question is how are these methods being added to the class. How does creating a new Writer class know to 'sync' up with my writer factory? I thought I was making a new Writer class for this test, but the strange thing is I still have access to other attributes from my writer factory. Hope this made sense, thanks.

Also, has anyone else done something similar in a test, is this the best way to handle this situation? It'd be nice if you tried to set a property that didn't exist that it would just create it, like it does in JavaScript.


Solution

  • The thing that is returned from your select query is probably not really a Writer model. It is probably an ActiveRecord::Relation. In order to provide the methods you have assigned values to in the SQL statement, it is probably implementing method_missing and checks some kind of internal hash setup by AREL.

    For mocking in tests, what you did will certainly work. Another option would be to use stub.

    it "should return a score based on the posted_posts and rejected_posts algorithm" do
      writer = Factory(:writer)
      writer.stub(:rejected_posts => 8)
      writer.stub(:posted_posts => 2)
      writer.performance_score.should == 80
    end