Находки Rails STI и Active Record

У меня есть набор классов примерно так:

class Owner
  has_one :animal
end

class Animal < AR::Base
  TYPES = ['Lion','Cat']
  # has a type field

  belongs_to :owner
end

class Lion < Animal
  def speak
    'roar'
  end
end

class Cat < Animal
  def speak
    'meow'
  end
end

Так что у меня есть ассоциация с владельцами и животными, и у каждого животного есть свой тип. Все это работает, пока я не сделаю такой вызов:

owner.animal.speak

Это возвращает объект типа «Животное», а затем пытается вызвать метод «говорить», который не определен для родителя. Как заставить Rails возвращать соответствующий подкласс вместо родительского класса?


person kid_drew    schedule 27.02.2015    source источник
comment
У вас есть столбец с именем типа в таблице животных?   -  person Doguita    schedule 28.02.2015
comment
Вы уверены, что рассматриваемое животное имеет тип? Вы проверяете наличие типа?   -  person evanbikes    schedule 28.02.2015
comment
Rails должен инициализировать экземпляр правильного подкласса. api.rubyonrails.org/classes/ActiveRecord/Inheritance.html Вы уверены? у вас правильно настроены модели?   -  person messanjah    schedule 28.02.2015


Ответы (2)


ActiveRecord и Arel нелегко обрабатывают такие подклассы. Лучше всего, чтобы класс Animal отслеживал свой собственный тип внутри. Так:

class Animal < AR::Base
  TYPES = ['Lion','Cat']

  belongs_to :owner

  def speak
     case self.type
     when "Lion"
        "roar"
     when "Cat"
        "meow"
     end
  end
end
person Joshua    schedule 27.02.2015
comment
Это одно из решений. Я собираюсь немного оставить это открытым, чтобы посмотреть, не придумал ли кто-нибудь более элегантный вариант. знак равно - person kid_drew; 28.02.2015

Я только что развернул новый проект Rails и использовал ваш пример, и он работал, как и ожидалось. Вы уверены, что в столбце type указан тип "Cat" или "Lion", а не строчная или подчеркнутая версия?

Я бы предложил добавить has_one :cat и has_one :lion к Owner. Затем, чтобы их создать, вы можете вызвать Rails owner.build_cat, который является has_one способом создания нового экземпляра. (В has_many будет owner.cats.build.)

Таким образом, вы уверены, что type выполнено правильно.

Это то, что я получаю.

> owner.animal
=> nil
> owner.cat
=> nil
> owner.build_cat
=> #<Cat id: nil, owner_id: 1, type: "Cat" ...
> owner.cat.save
=> true
> owner.cat 
=> #<Cat id: 1, owner_id: 1, type: "Cat" ...
> owner.animal
=> nil # Whaaaa?

(Экземпляр владельца имеет кеш ассоциации, и, поскольку мы уже вызвали животное, и оно было нулевым, оно было закэшировано как ноль)

> owner.clear_association_cache
=> {}
> owner.animal
=> #<Cat id: 1, owner_id: 1, type: "Cat" ...
> owner.animal.speak
=> 'meow'
> owner.animal.class
=> Cat
> owner.animal.class.to_s == owner.animal.type
=> true

Надеюсь, это поможет!

person evanbikes    schedule 01.03.2015