2010-11-04 2 views
1

Скажем, у меня есть класс, как:Динамический экземпляр Rails вложенного подкласса STI?

class Basket < ActiveRecord::Base 
    has_many :fruits 

Где «фрукты» является базовым классом STI, имеющий подклассов, как «яблоки», «апельсины», и т.д ...

Я хотел бы иметь возможность иметь метод сеттер в корзине, как:

def fruits=(params) 
    unless params.nil? 
    params.each_pair do |fruit_type, fruit_data| 
     fruit_type.build(fruit_data) 
    end 
    end 
end 

Но, очевидно, я получаю исключение, как:

NoMethodError (undefined method `build' for "apples":String) 

Обходной я думал как это работает:

def fruits=(params) 
    unless params.nil? 
    params.each_pair do |fruit_type, fruit_data| 
     "#{fruit_type}".create(fruit_data.merge({:basket_id => self.id})) 
    end 
    end 
end 

Но что заставляет объект Фруктовая ИППП быть создан до того Баскет класса, и поэтому ключ basket_id не сохраняется в подклассе Fruit (потому что basket_id Безразлично» t существует еще).

Я полностью в тупике. У кого-нибудь есть идеи?

ответ

1

Вместо добавления метод установки в корзине, добавить его в плодах:

class Fruit < ActiveRecord::Base 
    def type_setter=(type_name) 
    self[:type]=type_name 
    end 
end 

Теперь вы можете передать тип в том, когда вы строите объект через ассоциацию:

b = Basket.new 
b.fruits.build(:type_setter=>"Apple") 

Обратите внимание, что вы не можете назначить :type таким образом, так как он защищен от массового присвоения.

EDIT

Ах, вы хотите запустить различные функции обратного вызова в зависимости от подкласса? Правильно.

Вы можете сделать это:

fruit_type = "apples" 
b = Basket.new 
new_fruit = b.fruits << fruit_type.titleize.singularize.constantize.new 
new_fruit.class # Apple 

или определить has_many ассоциацию для каждого типа:

require_dependency 'fruit' # assuming Apple is defined in app/models/fruit.rb 

class Basket 
    has_many :apples 
end 

затем

fruit_type = "apples" 
b = Basket.new 
new_fruit = b.send(fruit_type).build 
new_fruit.class # Apple 
+0

Это работает для моих целей. Но если я правильно понимаю это, это означает, что любые обратные вызовы, которые могут быть у меня на моих подклассах STI, не будут выполняться, правильно? –

+0

К сожалению, да. См. Мой отредактированный ответ. – zetetic

+0

Удивительный! Большое спасибо! –

-1

В терминах Ruby "#{x}" просто эквивалентен , который для значений String точно совпадает с самой строкой. На других языках, таких как PHP, вы можете удалить ссылку на строку и рассматривать ее как класс, но здесь это не так. То, что вы, вероятно, означает это:

fruit_class = fruit_type.titleize.singularize.constantize 
fruit_class.create(...) 

Метод constantize преобразует строку в эквивалентный класс, но он чувствителен к регистру.

Имейте в виду, что вы раскрываете возможность того, что кто-то может создать что-то с fruit_type, установленным на "users", а затем выполните учетную запись администратора. Возможно, более ответственным является дополнительная проверка того, что то, что вы делаете, действительно относится к правильному классу.

fruit_class = fruit_type.titleize.singularize.constantize 
if (fruit_class.superclass == Fruit) 
    fruit_class.create(...) 
else 
    render(:text => "What you're doing is fruitless.") 
end 

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

fruit_class = Fruit::SUBCLASS_FOR[fruit_type] 

Вы можете определить эту константу, как это:

class Fruit < ActiveRecord::Base 
    SUBCLASS_FOR = { 
    'apples' => Apple, 
    'bananas' => Banana, 
    # ... 
    'zuchini' => Zuchini 
    } 
end 

Использование постоянная буквальный класса в вашей модели будет эффект загрузки их немедленно.

+0

OK, что это хорошая идея, но я все еще застряли. Корзина, похоже, не ищет класс, потому что мы создаем какой-то Фрукты через корзину has_many: fruit association. Так что это работает: Basket.fruits.create (params) или Basket.apples.create (params), но не Basket.fruit_class.create (params). –

+0

Вы не создали бы это через ассоциацию, так как сама ассоциация печатается. Что вы можете сделать, так это создать дочерний объект с объединенным свойством 'basket', как и в вашем вопросе. – tadman

+0

Но вы не можете этого сделать, потому что Basket.id еще не известно ... Корзина еще не сохранена. –

Смежные вопросы