Варианты шаблонов моделей рельсов (или наследования экземпляров)? - PullRequest
3 голосов
/ 21 ноября 2010

У меня есть ситуация, когда я хочу создавать «параметрические» модели в рельсах; например, я хотел бы определить PrototypeRecipe, а затем иметь возможность сделать несколько DerivedRecipe; может быть, один производный рецепт использует больше сахара, а другой использует меньше яиц или что-то еще. Критическим моментом является то, что я хочу, чтобы все «производные» экземпляры наследовали свойства от одного общего PrototypeRecipe, но могли выполнять локальные изменения.

В идеале, я хотел бы иметь возможность определять методы в прототипе (скажем, собирать список покупок), и эти методы реагируют на локальные изменения в производных экземплярах (поэтому, если я указал 3 яйца вместо 2, я мог бы вызвать функцию make_shopping_list прототипа, и это отражало бы это.

Существует ли существующий метод для достижения чего-то подобного? Вот лучшее, что я могу придумать:

class Ingredient << ActiveRecord::Base
    belongs_to :recipe, :polymorphic => true

    # uuid => UUID String (for grouping ingredients which change between prototype and derived instances)
end

class PrototypeRecipe << ActiveRecord::Base
    has_many :ingredients

    def make_ingredient_list(derived_recipe = nil)
        self.ingredients.map {|i| derived_recipe.nil? ? i : derived_recipe.ingredients.where(:ingredient_uuid => i.uuid).first }
    end
end

class DerivedRecipe << ActiveRecord::Base
    belongs_to :prototype_recipe

    has_many :ingredients

    def method_missing(sym, *args)
        self.prototype_recipe.send( sym, *args, self)
    end
end

Я знаю, что этот код можно сделать намного чище, мне больше интересно, можно ли улучшить общий подход. Основная идея заключается в том, что каждый ингредиент будет иметь уникальный идентификатор. Чтобы изменить рецепт прототипа, вы просто создаете экземпляр DerivedRecipe, связываете его с прототипом, а затем добавляете ингредиент с тем же UUID, что и у одного из ингредиентов прототипа.

1 Ответ

0 голосов
/ 11 января 2012

Я не на 100% от того, какое поведение вы ожидаете, поэтому вот мое попытанное решение.

Наследование в одной таблице (STI). Ваш базовый класс будет PrototypeRecipe, а ваш дочерний класс будет DerivedRecipe.

В таблице prototype_recipes укажите столбец type (текст). Это сигнализирует Rails, что вы хотите использовать STI. Если вы поместите свой метод make_ingredients_list в базовый класс, он будет доступен из ваших дочерних классов.

# app/models/ingredient.rb
class Ingredient < ActiveRecord::Base
  belongs_to :recipe, :class_name => "PrototypeRecipe"
  ...
end

# app/models/prototype_recipe.rb
class PrototypeRecipe < ActiveRecord::Base
  has_many :ingredients
  has_many :derived_recipes

  def make_ingredient_list
    ...
  end
end

# app/models/derived_recipe.rb
class DerivedRecipe < PrototypeRecipe
  belongs_to :prototype_recipe
end

Теперь вы можете сделать что-то вроде:

@cupcakes = PrototypeRecipe.create
@cupcakes_with_extra_eggs = @cupcakes.derived_recipes.create
print @cupcakes_with_extra_eggs.make_ingredient_list

Это то, что вы искали?

...