使用 STI 的 Rails 应用程序——提取这些记录的最简单方法?
我正在学习使用 Rails 的方法,并且正在开发一个示例应用程序来跟踪啤酒食谱.
I'm learning my way around Rails and am working on a sample app to keep track of beer recipes.
我有一个名为 Recipe 的模型,其中包含配方名称和效率.
I have a model called Recipe which holds the recipe name and efficiency.
我有一个名为 Ingredient 的模型,它使用 STI - 它被细分为麦芽、啤酒花和酵母.
I have a model called Ingredient which is using STI - this is subclassed into Malt, Hop, and Yeast.
最后,为了链接食谱和成分,我使用了一个名为 rec_items 的连接表,其中包含 recipe_id、成分 ID 和特定于该食谱/成分组合的信息,例如数量和煮沸时间.
Finally, to link the recipes and ingredients, I am using a join table called rec_items which holds the recipe_id, ingredient_id, and info particular to that recipe/ingredient combo, such as amount and boil time.
一切似乎都运行良好 - 我可以使用 Malt.all 找到我所有的麦芽,使用 Ingredient.all 找到所有成分.我可以使用@recipe.ingredients 等找到食谱的成分...
Everything seems to be working well - I can find all my malts by using Malt.all, and all ingredients by using Ingredient.all. I can find a recipe's ingredients using @recipe.ingredients, etc...
但是,我现在正在处理我的食谱的显示视图,并且对完成以下操作的最佳方法感到困惑:
However, I'm working on my recipe's show view now, and am confused as to the best way to accomplish the below:
我想显示配方名称和相关信息,然后列出成分,但按成分类型分隔.所以,如果我有一个效率为 85% 的黑色 IPA 并且它有 5 个麦芽和 3 个啤酒花品种,输出将类似于:
I want to display the recipe name and associated info, and then list the ingredients, but separated by ingredient type. So, if I have a Black IPA @ 85% efficiency and it has 5 malts and 3 hops varieties, the output would be similar to:
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 来提取这些类型.
Now, I can pull @recipe.rec_items and iterate through them, testing each rec_item.ingredient for type == "Malt", then do the same for the hops, but that doesn't seem very Rails-y nor efficient. So what is the best way to do this? I can use @recipe.ingredients.all to pull all the ingredients, but can't use @recipe.malts.all or @recipe.hops.all to pull just those types.
我应该使用不同的语法吗?我应该使用@recipe.ingredient.find_by_type("Malt") 吗?在控制器中执行此操作并将集合传递给视图,还是在视图中执行此操作?我是否还需要在 Hop 和 Malt 模型中指定 has_many 关系?
Is there a different syntax I should be using? Should I using @recipe.ingredient.find_by_type("Malt")? Doing this in the controller and passing the collection to the view, or doing it right in the view? Do I need to specify the has_many relationship in my Hop and Malt models as well?
我可以使用条件语句或 find_by_type 让它按照我想要的方式工作,但我的重点是用尽可能少的数据库开销来实现这种Rails 方式".
I can get it working the way I want using conditional statements or find_by_type, but my emphasis is on doing this "the Rails way" with as little DB overhead as possible.
感谢您的帮助!
当前的基本代码:
食谱.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
recipes_controller.rb
class RecipesController < ApplicationController
def show
@recipe = Recipe.find(params[:id])
end
def index
@recipes = Recipe.all
end
end
更新添加
我现在无法访问连接表属性,所以我发布了一个新问题:
I'm now unable to access the join table attributes, so I posted a new question:
Rails - 使用 group_by 和 has_many :through 并尝试访问连接表属性
如果有人能帮忙解决这个问题,我将不胜感激!!
If anyone can help with that, I'd appreciate it!!
自从我使用 STI 已经有一段时间了,已经被烧了一两次.所以我可能会跳过一些让这更容易的 STI-fu.话说……
It's been a while since I've used STI, having been burned a time or two. So I may be skipping over some STI-fu that would make this easier. That said...
有很多方法可以做到这一点.首先,您可以为每种麦芽、啤酒花和酵母制作一个范围.
There are many ways of doing this. First, you could make a scope for each of malt, hops, and yeast.
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
这将允许你做一些事情:
This will allow you to do something line:
malts = @recipe.ingredients.malt
hops = @recipe.ingedients.hops
虽然这很方便,但在数据库方面并不是最有效的做法.我们必须执行三个查询才能获得所有三种类型.
While this is convenient, it isn't the most efficient thing to do, database-wise. We'd have to do three queries to get all three types.
因此,如果我们不是在每个食谱中谈论大量成分,那么最好只提取所有 @recipe.ingredients,然后将它们分组,例如:
So if we're not talking a ton of ingredients per recipe, it'll probably be better to just pull in all @recipe.ingredients, then group them with something like:
ingredients = @recipe.ingredients.group_by(&:type)
这将执行一个查询,然后将它们分组到 ruby 内存中的一个散列中.散列将被关闭类型,看起来像:
This will perform one query and then group them into a hash in ruby memory. The hash will be keyed off of type and look something like:
{"Malt" => [first_malt, second_malt],
"Hops" => [first_hops],
"Yeast" => [etc]
}
然后您可以参考该集合以按您的意愿显示项目.
You can then refer to that collection to display the items however you wish.
ingredients["Malt"].each {|malt| malt.foo }