質問

I'm using STI (correctly, I promise!) for one relation of an object:

class Walrus < ActiveRecord::Base
  has_one :bubbles
end

class Bubbles < ActiveRecord::Base
  belongs_to :walrus
  before_save :set_origin

  private

  def set_origin
    self.type = walrus.state ? "Bubbles::#{walrus.state}" : 'Bubbles'
  end
end

class Bubbles::OfMind < Bubbles
  def tango
  end
end

Now if I build a new relation, the class isn't set correctly:

harold = Walrus.new(state: 'OfMind')
harold.build_bubbles.save!
harold.bubbles
  # => returns instance of Bubbles, not Bubbles::OfMind
harold.bubbles.tango
  # NoMethodError

The Bubbles object cannot magically become a Bubbles::OfMind, but until the relation is of the correct type, the correct functionality does not exist.

役に立ちましたか?

解決

Before addressing STI, watch out for model names and associations that break convention. I understand that you've chosen these class names for the sake of demonstration, but since you are running and testing this code, erratic behavior is no surprise.

Model class names should be singlular and sensible. Change the superclass to Bubble, and the subclass to something that refers to a variation of a bubble, such as BigBubble.

has_one associations must use singular model names as well: has_one :bubble.

Note: When Rails encounters a namespaced model, it expects the corresponding controller and view files to be namespaced as well, nested directories and all. It gets messy in a hurry. Best to avoid namespaces unless absolutely necessary.


Builder methods are a misuse of STI. Builder methods attempt to instantiate the superclass and manually assign it a type. This conflicts with Rails' built-in management of STI classes, hence no support for it.

STI superclasses are abstract classes that should never be instantiated. When working with STI, you must only interact with a subclass. All methods of a superclass are exposed in the subclasses, so there's no reason to touch a superclass object... unless to modify the type attribute which violates Rails' convention. If you absolutely must manipulate the superclass, STI shouldn't be used.

Properly done, you should create a subclass object directly with a manual association:

harold = Walrus.create!
BigBubble.create!(:walrus_id => harold.id)

harold.bubble
  # => returns instance of BigBubble

harold.bubble.tango
  # => true

Although not as elegant as a builder method, this way is proper and it works. Those blogs trying to solve namespaced STI association wonkiness (ahem...) are attempting to force behavior that isn't appropriate for STI to begin with. Proper usage of STI involves adopting the design guideline, "don't mess with the superclass."

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top