Это довольно типичная настройка таблицы соединения:
Здесь у нас есть нормализованная таблица с именем ingredients
, которая служит основной записью дляингредиент и recipe_ingredients
, который соединяется с таблицей recipe
.
В Rails мы настроим это как has_many through:
ассоциацию:
class Recipe < ApplicationRecord
has_many :recipe_ingredients
has_many :ingredients, through: :recipe_ingredients
end
class Ingredient < ApplicationRecord
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
end
class RecipeIngredient < ApplicationRecord
belongs_to :recipe
belongs_to :ingredient
end
Причина, по которой вы хотите использоватьhas_many through:
, а не has_and_belongs_to_many
- это то, что последний «безголовый», так как нет модели.Это может показаться хорошей идеей, пока вы не поймете, что нет способа получить доступ к дополнительным столбцам (например, к количеству).
При именовании таблиц соединения для has_many through:
вы должны следовать схеме [thing in singular]_[thing in plural]
благодаря тому, что Rails разрешает имена классов из имен таблиц.Например, использование recipes_ingredients
приведет к отсутствующей постоянной ошибке, так как Rails попытается загрузить Recipes::Ingredient
.Сама модель соединения должна называться SingularSingular
.
Конечно, вы также можете называть таблицы соединений любым именем, подходящим для домена.
Чтобы добавить вложенные строки, вы бы использовали вложенные атрибуты и fields_for :recipe_ingredients
:
<%= form_for(@recipe) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<fieldset>
<legend>Ingredients</legend>
<%= fields_for :recipe_ingredients do |ri| %>
<div class="nested_fields">
<div class="field">
<%= ri.label :ingredient %>
<%= ri.collection_select(:ingredient_id, Ingredient.all, :id, :name) %>
</div>
<div class="field">
<%= ri.number_field :quantity %>
</div>
</div>
<% end %>
</fieldset>
<% end %>
Однако вложенные поля во многих отношениях являются препятствием, позволяющим создавать / изменять несколько моделей одновременно.UX и поток приложений, объединяющий все вместе, вряд ли идеален.
Чтобы обеспечить хороший UX, возможно, лучше применить инкрементные сохранения (пользователь сохраняет рецепт перед добавлением ингредиентов в фоновом режиме с помощью AJAX) и добавитьингредиенты через серию запросов ajax POST к /recipies/:recipe_id/ingredients
.Но это тема всего учебного пособия сама по себе, и, скорее всего, к ней следует вернуться после того, как вы поймете основы.
См .: