使用STI的Rails应用程序 - 最简单的方法来提取这些记录?

时间:2011-08-25 14:46:30

标签: ruby-on-rails ruby-on-rails-3 sti

我正在学习Rails的方法,正在开发一个样本应用程序来跟踪啤酒食谱。

我有一个名为Recipe的模型,它保存食谱名称和效率。

我有一个名为Ingredient的模型正在使用STI - 它被分类为Malt,Hop和Yeast。

最后,为了链接配方和成分,我使用一个名为rec_items的连接表,它保存recipe_id,ingredient_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的类型==“麦芽”,然后对跳跃做同样的事情,但这似乎不是很好Rails-y也不高效。那么最好的方法是什么?我可以使用@ recipe.ingredients.all来提取所有成分,但不能使用@ recipe.malts.all或@ recipe.hops.all来提取这些类型。

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

我可以使用条件语句或find_by_type以我想要的方式工作,但我的重点是以尽可能少的DB开销来执行“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 - using group_by and has_many :through and trying to access join table attributes

如果有人可以提供帮助,我会很感激!!

3 个答案:

答案 0 :(得分: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 }

答案 1 :(得分: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

答案 2 :(得分: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。