2

我正在学习如何使用 Rails,并且正在开发一个示例应用程序来跟踪啤酒配方。

我有一个名为 Recipe 的模型,其中包含配方名称和效率。

我有一个使用 STI 的名为 Ingredient 的模型——它被细分为 Malt、Hop 和 Yeast。

最后,为了链接食谱和配料,我使用了一个名为 rec_items 的连接表,其中包含食谱 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-y,也不是很有效率。那么最好的方法是什么?我可以使用@recipe.ingredients.all 提取所有成分,但不能使用@recipe.malts.all 或@recipe.hops.all 提取这些类型。

我应该使用不同的语法吗?我应该使用@recipe.ingredient.find_by_type("Malt") 吗?在控制器中执行此操作并将集合传递给视图,还是在视图中正确执行?我是否还需要在我的 Hop 和 Malt 模型中指定 has_many 关系?

我可以使用条件语句或 find_by_type 让它按照我想要的方式工作,但我的重点是用尽可能少的 DB 开销来执行这种“Rails 方式”。

谢谢您的帮助!

当前的基本代码:

食谱.rb

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

成分.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 并尝试访问连接表属性

如果有人可以提供帮助,我将不胜感激!!

4

3 回答 3

3

自从我使用 STI 已经有一段时间了,已经烧了一两次了。所以我可能会跳过一些能让这更容易的 STI-fu。那就是说...

有很多方法可以做到这一点。首先,您可以为麦芽、啤酒花和酵母中的每一种制作一个范围。

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 }
于 2011-08-25T15:21:29.250 回答
3

你可以group_by在这里使用。

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

  ingredients.each do |ingredient|
    puts ingredient.inspect
  end
end
于 2011-08-25T15:23:53.623 回答
0

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。

于 2011-08-25T15:09:51.067 回答