用一个表单提交创建多个记录



我有3个模型:User, Ingredient和一个用户拥有哪些成分的映射- UserIngredient。

我当前的设置一次只能添加1种成分。我想要的是更新代码,这样用户就可以输入一些成分,只需点击"提交"即可。一次,而不是为每个成分单独点击。我已经查看了nested_resources,但它似乎不是正确的地方使用它。

正确的做法是什么?谢谢你

app/模型/user.rb

class User < ApplicationRecord
...
has_many :user_ingredients, dependent: :destroy
has_many :ingredients, through: :user_ingredients

...
end 

app/模型/ingredient.rb

class Ingredient < ApplicationRecord
...
has_many :user_ingredients, dependent: :destroy
has_many :owners, through: :user_ingredients
...
end

app/模型/user_ingredient.rb

class UserIngredient < ApplicationRecord
belongs_to :user
belongs_to :ingredient
validates :user, presence: true
validates :ingredient, presence: true
end

app/views/user_ingredients/new.html.erb

<div>
<%= turbo_frame_tag @user_ingredient do %>
<%= render "form", user_ingredient: @user_ingredient %>
<% end %>
</div>

app/views/user_ingredients/_form.html.erb

<div class="w-full mx-auto">
<%= form_for @user_ingredient do |f| %>
<div class="flex-row gap--md">
<%= f.select(
:ingredient_id,
options_from_collection_for_select(Ingredient.where(id: f.object.ingredient_id), :id, :name, :selected => f.object.ingredient_id),
{ prompt: 'Start typing to search' },
{ id: "drDwn_ingredient",
class: "w-full border border-black",
required: true,
data: { 
controller: "selectIngredient",
selectIngredient_url_value: autocomplete_ingredients_path,
},
}) %>
<div class="flex-row gap--xxxs">
<label>
<input type="submit" class="add_cancel_ing gap--md" />
<%= inline_svg_tag "svg/circle-check.svg", class: "svg_add_ing" %>
</label>
<%= link_to user_ingredients_path do %>
<%= inline_svg_tag "svg/circle-xmark.svg", class: 'svg_cancel_ing' %>
<% end %>
</div>
</div>
<% end %>
</div>

app/controllers/user_ingredients_controller.rb

class UserIngredientsController < ApplicationController
before_action :authenticate_user!
before_action :set_user_ingredient, only: [:show, :destroy]
def index
@user_ingredients = current_user.user_ingredients
end
def new
@user_ingredient = UserIngredient.new
end
def create
@user_ingredient = UserIngredient.new(user_ingredient_params.merge(user: current_user))
if @user_ingredient.save
respond_to do |format|
format.html { redirect_to user_ingredients_path, notice: 'Ingredient was successfully added to your bar!' }
format.turbo_stream { flash.now[:notice] = 'Ingredient was successfully added to your bar!' }
end
else
render :new
end
end
def destroy
@user_ingredient.destroy
respond_to do |format|
format.html { redirect_to user_ingredients_path, notice: "Ingredient was removed!" }
format.turbo_stream { flash.now[:notice] = "Ingredient was removed!" }
end
end
private
...
def set_user_ingredient
@user_ingredient = current_user.user_ingredients.find(params[:id])
end
def user_ingredient_params
params.require(:user_ingredient).permit(:id, :ingredient_id)
end
end

应用程序/javascript/控制器/selectIngredient_controller.js

import { Controller } from "@hotwired/stimulus";
import { get } from "@rails/request.js";
import TomSelect from "tom-select";
export default class extends Controller {
static values = { url: String };
multi_select_config = function () {
return {
plugins: ["remove_button", "no_active_items"],
valueField: "value",
load: (q, callback) => this.search(q, callback),
closeAfterSelect: true,
persist: false,
create: false,
};
};
async search(q, callback) {
const response = await get(this.urlValue, {
query: { q: q },
responseKind: "json",
});
if (response.ok) {
const list = await response.json;
callback(list);
} else {
console.log("Error in select_ctrl: ");
console.log(response);
callback();
}
}
connect() {
new TomSelect(this.element, this.multi_select_config());
}
}

对于User应该使用accepts_nested_attributes_for方法并尝试通过User创建相关记录。

https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

或者您可以尝试为一次接受带有多个记录的自定义表单创建自定义操作。但是第一个选项更容易预测,也更容易支持。

对于视图,可以使用cocoongem。它很旧了,但仍然很好用。或者你也可以从中得到启发,做出自己的解决方案)https://github.com/nathanvda/cocoon

最新更新