Я просмотрел StackOverflow и не смог найти решение для моего варианта использования здесь.
Я пытаюсь прикрепить команду или несколько команд к отступлениям при создании отступлений.
У меня мультитенантное приложение, у которого есть владелец аккаунта, который приглашает пользователей. Пользователи могут быть добавлены в команды.
Я могу успешно создать ретрит, если ни одна команда не выбрана в форме, однако, когда команда выбрана и ретрит создан с этой выбранной командой, я получаю следующую ошибку:
ActiveModel::MissingAttributeError in Accounts::RetreatsController#create
can't write unknown attribute `retreat_id`
Parameters:
{"authenticity_token"=>"AcmWMmv+rhwzLKsxi9RykkhhquaZuTBqSBj87acOdDOo+uXycQOWHoeuNzDEGTQw+o7ewwS4EB8uTyIy7x5vsA==",
"retreat"=>{"name"=>"bbb", "description"=>"dsfsdf", "team_ids"=>["56"]},
"commit"=>"Create Retreat"}
Вот мои модели:
team.rb
class Team < ApplicationRecord
belongs_to :account
has_many_and_belongs_to :retreat
has_many :team_members
has_many :users, through: :team_members
accepts_nested_attributes_for :team_members
end
retreat.rb
class Retreat < ApplicationRecord
belongs_to :user
belongs_to :account
validates :name, presence: true, uniqueness: true
has_many :teams
accepts_nested_attributes_for :teams
end
Это контроллер:
module Accounts
class RetreatsController < Accounts::BaseController
before_action :authenticate_user!
before_action :set_retreat, only: [:show, :edit, :update, :destroy]
def index
@retreats = current_account.retreats.all.order("created_at DESC")
end
def show
end
def new
@retreat = current_account.retreats.build
@team = current_account.teams.all
end
def edit
end
def create
@retreat = current_account.retreats.build(retreat_params)
@retreat.user_id = current_user.id
respond_to do |format|
if @retreat.save
format.html { redirect_to @retreat, notice: 'Retreat was successfully created.' }
format.json { render :show, status: :created, location: @retreat }
else
format.html { render :new }
format.json { render json: @retreat.errors, status: :unprocessable_entity }
end
end
end
def destroy
@retreat.destroy
respond_to do |format|
format.html { redirect_to root_path, notice: 'Retreat was successfully deleted.' }
format.json { head :no_content }
end
end
private
def set_retreat
@retreat = current_account.retreats.find(params[:id])
end
def retreat_params
params.require(:retreat).permit(:name, :description, team_ids:[])
end
end
end
Это представление:
<div class="w-full lg:w-12/12 bg-white p-5 rounded-lg lg:rounded-l-none">
<%= simple_form_for(@retreat) do |f| %>
<%= f.error_notification %>
<form class="px-8 pt-6 pb-8 mb-4 bg-white rounded">
<div class="mb-4">
<div class="w-full mb-4 md:mr-2 md:mb-0">
<label class="block mb-2 text-sm font-bold text-gray-700" for="firstName">
Name your retreat
</label>
<%= f.input :name, autofocus: true, class: 'w-full px-3 py-2 mb-3 text-sm leading-tight text-gray-700 border rounded shadow appearance-none focus:outline-none focus:shadow-outline', label: false %>
</div>
</div>
<div class="mb-4">
<div class="w-full mb-4 md:mr-2 md:mb-0">
<label class="block mb-2 text-sm font-bold text-gray-700" for="firstName">
Give it a description
</label>
<%= f.text_area :description, autofocus: true, class: 'no-resize appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white focus:border-gray-500 h-48 resize-none', label: false %>
</div>
</div>
<div class="field">
<div class="control">
<%= f.label "Add team(s) to your retreat", class:"label" %><br />
<div class="control has-icons-left">
<%= f.collection_check_boxes :team_ids, current_account.teams.all, :id, :name, include_hidden: false do |b| %>
<div class="collection-check-box">
<%= b.check_box %>
<%= b.label %>
</div>
<% end %>
</div>
</div>
</div>
<div class="field">
<div class="control">
<%= f.button :submit, class: "w-full px-4 py-2 font-bold text-white rounded-full bg-green-500 hover:bg-green-700 focus:outline-none focus:shadow-outline" %>
</div>
</div>
<%= link_to 'Back', retreats_path, class:'button' %>
</form>
<% end %>
</div>
Вот моя схема:
create_table "retreats", force: :cascade do |t|
t.string "name"
t.text "description"
t.bigint "user_id", null: false
t.bigint "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "team_id"
t.index ["account_id"], name: "index_retreats_on_account_id"
t.index ["user_id"], name: "index_retreats_on_user_id"
end
create_table "teams", force: :cascade do |t|
t.string "name"
t.bigint "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_teams_on_account_id"
end
create_table "team_members", force: :cascade do |t|
t.bigint "team_id", null: false
t.bigint "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["team_id"], name: "index_team_members_on_team_id"
t.index ["user_id"], name: "index_team_members_on_user_id"
end
Ответ
В итоге я добавил таблицу соединения между и команды по отступлению, и команды по отступлению, и объединяющий стол со ссылками как на отступление, так и на команды.
В принятом комментарии предлагалось использовать ассоциацию has_and_belongs_to_many, но я считаю, что это не рекомендуется для текущих версий rails. И, следовательно, используется сквозной, который принимает вложенные атрибуты.