user3750194
user3750194

Reputation: 407

Why is an instance of my class seen as an array?

Just a quick question. I have a test specification that looks like so...

require 'user'

describe "Integration" do
  let(:user) { User.new(voucher) }

  context 'no voucher' do
    let(:voucher) { nil }
context 'no voucher' do
let(:voucher) { nil }

    it 'should bill default price all the time' do
        user.bill
        expect(user.orders[0].billed_for).to eql 6.95
        ... ...
    end
  end

  context 'vouchers' do
    describe 'default vouchers' do
      let(:voucher) { Voucher.create(:default, credit: 15) }

      it 'should not bill user if has a remaining credit' do
        user.bill
        expect(user.orders[0].billed_for).to eql 0.0
        ... ...
      end
    end

I have a very simple voucher class atm

class Voucher
    attr_accessor :credit, :type

    def self.create(type, *attrs)
        @type = type
    end

    def billed_for
        Random.new.rand(0..4)
    end
end

Now what's weird is If I try to access the variables of the voucher class through user where it's stored it throws up and undefined variable. The object definitely isn't nil, when I check the type it tells me it's an array! Printing this produces stuff like... [{:credit=>15}][{:discount=>50, :number=>3}] Why is this happening? Have I set something wrong in my user class? My user class is below

require 'order'
require 'voucher'

class User
    attr_accessor :voucher, :orders

    def initialize(voucher = nil, orders =[])
        @orders = orders
        @voucher = voucher.nil? ? nil : voucher
    end

Upvotes: 2

Views: 102

Answers (1)

hjing
hjing

Reputation: 4982

Your voucher.create method isn't actually creating a voucher. If you check the class of the return result of Voucher.create, you should see Symbol:

Voucher.create(:default).class #=> Symbol

This is because you're returning the result of setting @type = type, where type is :default, which has a class of Symbol. I'm not sure where the array is coming from, but in order to create a proper Voucher, you should override the initialize method, much like you're doing in the User class:

class Voucher
  def initialize(type, *attrs)
    @type = type
    # you should probably do something with attrs as well.
  end
end

Now you can call Voucher.new to build an initialized Voucher object:

Voucher.new(:default).class #=> Voucher

If you want to keep the same api, you can then define self.create as follows:

class Voucher
  def self.create(type, *attrs)
    new(type, *attrs)
  end
end

Voucher.create(:default, :some, :more, :attrs).class #=> Voucher

As an aside, you don't really need that nil check in the initialize method of your User class, the following is equivalent:

class User
  def initialize(voucher = nil, orders =[])
    @orders = orders
    @voucher = voucher
  end
end

Looking at your tests, Voucher's create method takes two parameters, a voucher type and an attributes hash.

Voucher.create(:default, credit: 15, discount: 50)

is equivalent to

Voucher.create(:default, { credit: 15, discount: 50 })

so you probably want to define Voucher's initialize method as follows:

class Voucher
  def initialize(type, attrs = {})
    @type = type
    # iterate through the attributes hash and assign attributes appropriately 
  end
end

Assuming you have defined setters for each of the attribute keys you are assigning, you could do the following:

def initialize(type, attrs = {})
  @type = type
  attrs.each do |key, value|
    send("#{key}=", value) 
  end
end

See documentation of send here.

Given the following line of code:

Voucher.new(:default, credit: 15, discount: 50)

The following logic would happen inside the voucher's initialize function:

@type = :default
# iterate through the hash { credit: 15, discount: 50 }
self.credit=(15)   # equivalent to self.credit = 15
self.discount=(50) # equivalent to self.discount = 50

and you wind up with a properly initialized voucher object.

It is straightforward to modify your create method appropriately.

Upvotes: 1

Related Questions