我正在尝试找到基于其关联和搜索数组选择记录的最简洁方法。
我有Recipes
有很多Ingredients
(通过连接表)我有一个搜索表单字段为array
的Ingredient.ids
要查找包含搜索数组中任何ID的任何配方,我可以使用
例如1。
filtered_meals = Recipe.includes(:ingredients).where("ingredients.id" => ids)
但是,我想只匹配在搜索数组中找到所有成分的食谱。
例如2。
search_array = [1, 2, 3, 4, 5]
Recipe1 = [1, 4, 5, 6]
Recipe2 = [1, 3, 4]
# results => Recipe2
我知道我可以使用each
循环,就像这样;
例如3。
filtered_meals = []
Recipes.each do |meal|
meal_array = meal.ingredients.ids
variable = meal_array-search_array
if variable.empty?
filtered_meals.push(meal)
end
end
end
return filtered_meals
这里的问题是分页。在第一个例子中,我可以使用.limit()
和.offset()
来控制显示多少结果,但在第三个例子中,我需要添加一个额外的计数器,提交结果,然后在页面更改时,重新发送计数器并在.drop(counter)
循环上使用each.do
。
这似乎太长了,有没有更好的方法呢?
假设您使用的是has_many through
和recipe_id,则ingredient_id组合是唯一的。
recipe_ids = RecipeIngredient.select(:recipe_id)
.where(ingredient_id: ids)
.group(:recipe_id)
.having("COUNT(*) >= ?", ids.length)
filtered_meals = Recipe.find recipe_ids
Ruby on Rails Guide 2.3.3 - Subset Conditions
Recipe.all(:ingredients => { :id => search_array })
应该导致:
SELECT * FROM recipes WHERE (recipes.ingredients IN (1,2,3,4,5))
在SQL中。
数组&
运算符会在这里为你工作吗?
就像是:
search_array = [1, 2, 3, 4, 5]
recipe_1 = [1, 4, 5, 6]
recipe_2 = [1, 3, 4]
def contains_all_ingredients?(search_array, recipe)
(search_array & recipe).sort == recipe.sort
end
contains_all_ingredients(search_array, recipe_1) #=> false
contains_all_ingredients(search_array, recipe_2) #=> true
此方法比较数组并仅返回两者中存在的元素,因此如果比较结果等于配方数组,则全部都存在。 (显然,您可以通过一些重构来使方法位于配方模型中。)
然后你可以这样做:
Recipes.all.select { |recipe| contains_all_ingredients?(search_array, recipe) }
我不确定它通过你的例子三,但可能会帮助你的方式?让我知道如果开始没问题,我会在此期间有更多的想法/如果它有用:)
怎么样
filtered_meals = Recipe.joins(:ingredients)
.group(:recipe_id)
.order("ingredients.id ASC")
.having("array_agg(ingredients.id) = ?", ids)
您需要确保您的ids参数按升序列出,以便数组中元素的顺序也匹配。