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

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

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

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

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

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

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

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

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

Есть ли другой синтаксис, который мне следует использовать? Стоит ли использовать @ recipe.ingredient.find_by_type ("Солод")? Делать это в контроллере и передавать коллекцию в представление или делать это прямо в представлении? Нужно ли мне также указывать отношение 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

Солод.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 и попытка доступа к атрибутам таблицы соединений

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

2
Jim 25 Авг 2011 в 18:46

3 ответа

Лучший ответ

Я давно не употреблял ИППП, раз или два обгорел. Так что, возможно, я пропущу некоторые 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)

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

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

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

ingredients["Malt"].each {|malt| malt.foo }
3
JofoCodin 25 Авг 2011 в 15:21

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

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

  ingredients.each do |ingredient|
    puts ingredient.inspect
  end
end
3
rubish 25 Авг 2011 в 15:23

Полезность 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, если вы сортируете по столбцу типа.

0
tadman 25 Авг 2011 в 15:37