I would like to be able to call the build method on a scope that targets a certain class of model via its STI type, and have ActiveRecord build an instance of the correct class.
class Post < ActiveRecord::Base
scope :special, where(type: 'SpecialPost')
class SpecialPost < Post; end
> Post.special.build # Expect an instance of SpecialPost here
=> #<Post ...>
Here, I expected an instance of SpecialPost, not an instance of Post.
This does not work, and I believe it's right the way it is. Setting the type attribute in a scope doesn't mean Active Record will build that particular type object when using STI, it only means it'll set that type attribute when building your post.
So, doing Post.special.build should give you a Post with a SpecialPost type, nothing more. If you save and reload, you'd probably get a SpecialPost instead.. And if you want a new instance of SpecialPost, you'd have to do SpecialPost.build instead.
I respectfully disagree. Using the exact same code above:
=> #<SpecialPost ...>
=> #<Post ...>
Surely we can't advocate for a design where the association getter does one thing, and the association builder does another.
special is an association or a scope? if it is a scope this behavior is right.
You are correct that I misspoke. I meant to say “scope getter does one thing, and the scope builder does another.”
My claim stands: for Model.scope.first to return an instance of one class and Model.scope.build to return an instance of a different class breaks my expectation of what that scope represents.
A scope can't know anything about STI. Scopes always return the caller class instance when you are using the build method, this is expected behavior.
So, are we sure that we don't want this to work?
scope :canadian, where(type: 'CanadianPerson')
scope :american, where(type: 'AmericanPerson')
scope :swedish, where(type: 'SwedishPerson')
nationality = [:canadian, :american, :swedish, ...]
p = Citizen(nationality.sample).build
=> #<CanadianCitizen ...>
We can make this work. But right now it is not an issue. You can either open a pull request or discuss in the Rails Core mailing list.
Rails 4.0 seems to have rolled this in.