我有模型游戲、玩家和國家。我正在處理一個帶有嵌套欄位的表單Player,應該為其創建具有相關國家/地區的播放器。
的陣列作為值陣列country_ids通過 a 發送。nested_player.hidden_field :country_ids
游戲:
class Game < ApplicationRecord
has_and_belongs_to_many :players
accepts_nested_attributes_for :players
end
玩家:
class Player < ApplicationRecord
has_and_belongs_to_many :games
has_many :countries
end
國家:
class Country < ApplicationRecord
belongs_to :player, optional: true
end
游戲控制器:
def game_params
params.require(:game).permit(:metadata, players_attributes: [:name, :color, country_ids: []])
end
形式:
<%= simple_form_for @game do |f| %>
<%= f.fields_for :players do |player| %>
<%= player.input :name %>
<%= player.input :color, as: :color %>
<%= player.hidden_field :country_ids, value: ["226"] %>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
問題:
控制器country_ids按預期接收。保存了Game 和 nested Player,但沒有建立玩家國家關聯。
引數:
{"game"=>{"players_attributes"=>{"0"=>{"name"=>"foo", "color"=>"#000000", "country_ids"=>"226"}}},
"commit"=>"Submit"}
uj5u.com熱心網友回復:
您現在的設定方式將在Country#player_id您每次使用新玩家創建新游戲時重新分配,最后一個玩家 id 將在Country#player_id;現在是一個國家 -> 一個玩家。
要修復它,請在 Country 和 Player 之間添加另一個連接表。
# db/migrate/20220419040615_create_pink_floyd90_game.rb
class CreatePinkFloyd90Game < ActiveRecord::Migration[7.0]
def change
create_table :countries do |t|
t.string :name
end
create_table :games do |t|
t.string :name
end
create_table :players do |t|
t.string :name
end
create_join_table :countries, :players # fixed
create_join_table :games, :players
end
end
# app/models/*.rb
class Country < ApplicationRecord
has_and_belongs_to_many :players
end
class Player < ApplicationRecord
has_and_belongs_to_many :games
has_and_belongs_to_many :countries
end
class Game < ApplicationRecord
has_and_belongs_to_many :players
accepts_nested_attributes_for :players
end
在設定表單之前,如果關聯不明顯,最好測驗關聯:
>> Country.create!([{name: 'Country 1'}, {name: 'Country 2'}])
>> Game.create!(name: 'Game 1', players_attributes: {one: {name: 'Player 1', country_ids: [1,2]}})
# NOTE: notice the actual records that are created, and make sure this is the intention
# Load the referenced by ids countries (for validation, I think)
Country Load (0.7ms) SELECT "countries".* FROM "countries" WHERE "countries"."id" IN ($1, $2) [["id", 1], ["id", 2]]
TRANSACTION (0.3ms) BEGIN
# Create a game
Game Create (0.7ms) INSERT INTO "games" ("name") VALUES ($1) RETURNING "id" [["name", "Game 1"]]
# Create a player
Player Create (0.7ms) INSERT INTO "players" ("name") VALUES ($1) RETURNING "id" [["name", "Player 1"]]
# Associate 'Country 1' with 'Player 1'
Player::HABTM_Countries Create (0.5ms) INSERT INTO "countries_players" ("country_id", "player_id") VALUES ($1, $2) [["country_id", 1], ["player_id", 1]]
# Associate 'Country 2' with 'Player 1'
Player::HABTM_Countries Create (0.3ms) INSERT INTO "countries_players" ("country_id", "player_id") VALUES ($1, $2) [["country_id", 2], ["player_id", 1]]
# Associate 'Game 1' with 'Player 1'
Game::HABTM_Players Create (0.5ms) INSERT INTO "games_players" ("game_id", "player_id") VALUES ($1, $2) [["game_id", 1], ["player_id", 1]]
TRANSACTION (2.8ms) COMMIT
=> #<Game:0x00007f3ca4a82540 id: 1, name: "Game 1">
>> Game.first.players.pluck(:name)
=> ["Player 1"]
>> Player.first.countries.pluck(:name)
=> ["Country 1", "Country 2"]
現在我知道這是有效的,任何意外都會出現在控制器或表單中。這是第二個問題所在,也是玩家與國家沒有關聯的原因。
{
"game"=>{
"players_attributes"=>{
"0"=>{
"name"=>"foo",
"color"=>"#000000",
"country_ids"=>"226" # doesn't look like an array
}
}
},
"commit"=>"Submit"
}
因為country_ids它不是一個陣列,也不是一個哈希,rails 將其識別為陣列{ "0"=>{}, "1"=>{} }允許的引數不允許它通過。
Game.create(game_params) # <= never receives `country_ids`
易于檢查 Rails 控制臺
https://api.rubyonrails.org/classes/ActionController/Parameters.html
# this is a regular attribute, not an array
>> params = {"country_ids"=>"1"}
>> ActionController::Parameters.new(params).permit(country_ids: []).to_h
=> {} # not allowed
# how about a nested hash
>> params = {"country_ids"=>{"0"=>{"id"=>"1"}}}
>> ActionController::Parameters.new(params).permit(country_ids: [:id]).to_h
=> {"country_ids"=>{"0"=>{"id"=>"1"}}} # allowed, but not usable without modifications
# how about an array
>> params = {"country_ids"=>["1","2"]}
>> ActionController::Parameters.new(params).permit(country_ids: []).to_h
=> {"country_ids"=>["1", "2"]} # TADA!
如何讓表單提交陣列。
提交一個實際的陣列有點麻煩。訣竅是使輸入name屬性以[]. 對于country_ids,輸入應如下所示
<input value="1" type="text" name="game[players_attributes][0][country_ids][]">
<input value="2" type="text" name="game[players_attributes][0][country_ids][]">
# this will submit these parameters
# {"game"=>{"players_attributes"=>{"0"=>{"country_ids"=>["1", "2"]}}}
表單構建器似乎不喜歡這種設定,所以我們必須做一些惡作劇,尤其是對于這種嵌套設定:
form_for
<% game.players.build %>
<%= form_with model: game do |f| %>
<%= f.fields_for :players do |ff| %>
<%# using plain tag helper %>
<%# NOTE: `ff.object_name` returns "game[players_attributes][0]" %>
<%= text_field_tag "#{ff.object_name}[country_ids][]", 1 %> <%# <input type="text" name="game[players_attributes][0][country_ids][]" id="game_players_attributes_0_country_ids_" value="1"> %>
<%= text_field_tag "#{ff.object_name}[country_ids][]", 2 %> <%# <input type="text" name="game[players_attributes][0][country_ids][]" id="game_players_attributes_0_country_ids_" value="2"> %>
<%# using `fields_for` helper %>
<%= ff.fields_for :country_ids do |fff| %>
<%# NOTE: empty string '' gives us [] %>
<%= fff.text_field '', value: 1 %> <%# <input value="1" type="text" name="game[players_attributes][0][country_ids][]" id="game_players_attributes_0_country_ids_"> %>
<%= fff.text_field '', value: 2 %> <%# <input value="2" type="text" name="game[players_attributes][0][country_ids][]" id="game_players_attributes_0_country_ids_"> %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
simple_form一樣
<% game.players.build %>
<%= simple_form_for game do |f| %>
<%= f.simple_fields_for :players do |ff| %>
<%= ff.simple_fields_for :country_ids do |fff| %>
<%= fff.input '', input_html: { value: 1 } %> <%# <input value="1" type="text" name="game[players_attributes][0][country_ids][]" id="game_players_attributes_0_country_ids_"> %>
<%= fff.input '', input_html: { value: 2 } %> <%# <input value="2" type="text" name="game[players_attributes][0][country_ids][]" id="game_players_attributes_0_country_ids_"> %>
<% end %>
<% end %>
<% end %>
或者忘記這一切,因為它很復雜并且country_ids作為純字串并將其拆分在控制器中
<%= simple_form_for game do |f| %>
<%= f.simple_fields_for :players do |ff| %>
<%= ff.input :country_ids, input_html: { value: [1,2] } %> <%# <input value="1 2" type="text" name="game[players_attributes][0][country_ids]" id="game_players_attributes_0_country_ids"> %>
<% end %>
<%= f.submit %>
<% end %>
def game_params
# modify only once
@game_params ||= modify_params(
params.require(:game).permit(players_attributes: [:name, :country_ids])
)
end
def modify_params permitted
# NOTE: take "1 2" and split into ["1", "2"]
permitted[:players_attributes].each_value{|p| p[:country_ids] = p[:country_ids].split }
permitted
end
def create
Game.create(game_params)
end
希望這不會太混亂。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/460244.html
