Приложение Rails с использованием STI - самый простой способ получить эти записи? - PullRequest
2 голосов
/ 25 августа 2011

Я изучаю Rails и работаю над примером приложения для отслеживания рецептов пива.

У меня есть модель под названием Рецепт, которая содержит название и эффективность рецепта.

У меня есть модель Ingredient, которая использует STI - она ​​подразделяется на солод, хмель и дрожжи.

Наконец, чтобы связать рецепты и ингредиенты, я использую соединительную таблицу rec_items, которая содержит recipe_id, component_id и информацию, специфичную для этого рецепта / ингредиента, такую ​​как количество и время варки.

Кажется, все работает хорошо - я могу найти все свои солоды, используя Malt.all, и все ингредиенты, используя Ingredient.all. Я могу найти ингредиенты рецепта, используя @ recipe.ingredients и т.д ...

Тем не менее, я сейчас работаю над представлением моего рецепта, и я не понимаю, как лучше всего выполнить следующее:

Я хочу отобразить название рецепта и связанную с ним информацию, а затем перечислить ингредиенты, но разделенные по типу ингредиента. Итак, если у меня эффективность Black IPA @ 85% и у меня 5 сортов солода и 3 сорта хмеля, результат будет примерно таким:

BLACK IPA (85%)
Ingredient List
MALTS:
malt 1
malt 2
...
HOPS:
hop 1
...

Теперь я могу вытащить @ recipe.rec_items и выполнить итерацию по ним, проверяя каждый rec_item.ingredient для типа == "Malt", а затем сделать то же самое для прыжков, но это не кажется ни Rails-y, ни эффективным , Так каков наилучший способ сделать это? Я могу использовать @ recipe.ingredients.all, чтобы вытащить все ингредиенты, но не могу использовать @ recipe.malts.all или @ recipe.hops.all, чтобы вытащить только эти типы.

Есть ли другой синтаксис, который я должен использовать? Должен ли я использовать @ recipe.ingredient.find_by_type ("Malt")? Делаете это в контроллере и передаете коллекцию в представление, или делаете это прямо в представлении? Нужно ли также указывать отношение has_many в моих моделях хмеля и солода?

Я могу заставить его работать так, как я хочу, используя условные операторы или find_by_type, но я делаю упор на этом "пути Rails" с минимальными издержками БД, насколько это возможно.

Спасибо за помощь!

Текущий код:

Recipe.rb

class Recipe < ActiveRecord::Base
  has_many :rec_items
  has_many :ingredients, :through => :rec_items
end

Ingredient.rb

class Ingredient < ActiveRecord::Base
  has_many :rec_items
  has_many :recipes, :through => :rec_items
end

Malt.rb

class Malt < Ingredient
end

Hop.rb

class Hop < Ingredient
end

RecItem.rb

class RecItem < ActiveRecord::Base
  belongs_to :recipe
  belongs_to :ingredient
end

recipes_controller.rb

class RecipesController < ApplicationController
  def show
    @recipe = Recipe.find(params[:id])
  end

  def index
    @recipes = Recipe.all
  end
end

Обновлено для добавления

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

Rails - использование group_by и has_many: through и попытка доступа к атрибутам таблицы объединения

Если кто-то может помочь с этим, я был бы признателен !!

Ответы [ 3 ]

3 голосов
/ 25 августа 2011

Прошло много времени с тех пор, как я использовал STI, будучи сожженным раз или два. Так что я могу пропустить какую-нибудь STI-фу, которая облегчила бы это. Тем не менее ...

Есть много способов сделать это. Во-первых, вы можете создать область действия для каждого из солода, хмеля и дрожжей.

class Ingredient < ActiveRecord::Base
  has_many :rec_items
  has_many :recipes, :through => :rec_items
  named_scope :malt, :conditions =>  {:type => 'Malt'}
  named_scope :hops, :conditions => {:type => 'Hops'}
  ...
end

Это позволит вам сделать что-то строковое:

malts = @recipe.ingredients.malt
hops = @recipe.ingedients.hops

Хотя это удобно, это не самая эффективная вещь для базы данных. Нам нужно сделать три запроса, чтобы получить все три типа.

Так что, если мы не говорим по тонне ингредиентов на рецепт, вероятно, будет лучше просто включить все @ recipe.ingredients, а затем сгруппировать их как-то так:

ingredients = @recipe.ingredients.group_by(&:type)

Это выполнит один запрос, а затем сгруппирует их в хэш в памяти ruby. Хеш будет иметь тип ключа и будет выглядеть примерно так:

{"Malt" => [first_malt, second_malt],
 "Hops" => [first_hops],
 "Yeast" => [etc]
}

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

ingredients["Malt"].each {|malt| malt.foo }
3 голосов
/ 25 августа 2011

Вы можете использовать group_by здесь.

recipe.ingredients.group_by {|i| i.type}.each do |type, ingredients|
  puts type

  ingredients.each do |ingredient|
    puts ingredient.inspect
  end
end
0 голосов
/ 25 августа 2011

Утилита STI в этом случае сомнительна. Вы могли бы быть лучше с прямой категоризацией:

class Ingredient < ActiveRecord::Base
  belongs_to :ingredient_type

  has_many :rec_items
  has_many :recipes, :through => :rec_items
end

IngredientType определяет ваши различные типы и становится числовой константой с этого момента.

Когда вы пытаетесь отобразить список, это становится проще. Я обычно предпочитаю извлекать промежуточные записи напрямую, а затем присоединяюсь по мере необходимости:

RecItem.sort('recipe_id, ingredient_type_id').includes(:recipe, :ingredient).all

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

...