smathy
smathy

Reputation: 27961

Undefined method error for scope on STI subclass

The Setup

I have an STI setup like so:

class Transaction < ActiveRecord::Base
  belongs_to :account

  scope :deposits, -> { where type: Deposit }
end

class Deposit < Transaction
  scope :pending, -> { where state: :pending }
end

class Account < ActiveRecord::Base
  has_many :transactions
end

If I call:

> a = Account.first
> a.transactions.deposits

...then I get what I expect, a collection of Deposit instances, however if I look at the class of what's returned:

> a.transactions.deposits.class

...then it's actually not a Deposit collection, it's still a Transaction collection, ie. it's a Transaction::ActiveRecord_AssociationRelation

The Problem

So, to the problem, if I then want to call one of the Deposit scopes on that collection it fails:

> a.transactions.deposits.pending
NoMethodError: undefined method `pending' for #<Transaction::ActiveRecord_Associations_CollectionProxy:0x007f8ac1252d00>

Things I've Checked

I've tried changing the scope to Deposit.where... which had no effect, and also to Deposit.unscoped.where... which actually returns the right collection object, but it strips all the scope, so I lose the account_id=123 part of the query so it fails on that side.

I've checked this and the problem exists for both Rails 4.1 and 4.2. Thanks for any pointers on how to make this work.

I know there's a workaround, but...

I know I could work around the issue by adding a has_many :deposits into Account, but I'm trying to avoid that (in reality I have many associated tables and many different transaction subclasses, and I'm trying to avoid adding the dozens of extra associations that would require).

Question

How can I get what's returned by the deposits scope to actually be a Deposit::ActiveRecord_Association... so that I can chain my scopes from Deposit class?

Upvotes: 3

Views: 1094

Answers (3)

adantj
adantj

Reputation: 466

I created an isolated test for your issue here:https://gist.github.com/aalvarado/4ce836699d0ffb8b3782#file-sti_scope-rb and it has the error you mentioned.

I came across this post from pivotal http://pivotallabs.com/merging-scopes-with-sti-models/ about using were_values in a scope to get all the conditions. I then used them on unscope to force the expected class, basically this:

  def self.deposits
    conditions = where(nil).where_values.reduce(&:and)
    Deposit.unscoped.where(conditions)
  end

This test asserts that it returns a Deposit::ActiveRecord_Relation https://gist.github.com/aalvarado/4ce836699d0ffb8b3782#file-sti_scope2-rb

Update

You can also write this as a scope if you prefer:

  scope :deposits, -> { Deposit.unscoped.where where(nil).where_values.reduce &:and }

Upvotes: 3

rubenrails
rubenrails

Reputation: 176

As a quick workaround you can do > a.transactions.deposits.merge(Deposit.pending), but can't think of a different way of solving it. I'll think and try more options later and come back if I find anything.

Upvotes: 2

messanjah
messanjah

Reputation: 9278

You might want to say that an Account has_many :deposits

class Account < ActiveRecord::Base
  has_many :transactions
  has_many :deposits
end

Then you should be able to query

a.deposits.pending

Upvotes: 1

Related Questions