Reputation: 27961
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
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>
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 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).
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
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
You can also write this as a scope if you prefer:
scope :deposits, -> { Deposit.unscoped.where where(nil).where_values.reduce &:and }
Upvotes: 3
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
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