Ruby on Rails - 保存一个模型相对于另一个模型的表单数据

Ruby on Rails - Save form data of one model in relation to another model

我再次修改了问题以包含控制器文件。

在我们的应用程序中,我们有三个模型。用户模型、记分板模型、团队模型。一个用户 has_many 记分板和一个记分板 belongs_to 一个用户。记分板控制器中用于关联两者的创建操作的代码是“@scoreboard = current_user.scoreboards.build”。这段代码工作得很好。

现在,第三个模型出现了问题。记分牌模型 has_many 个团队,每个团队 belongs_to 个记分牌。是has_many、belongs_to关系。因此,外键在团队 table 上。下面分别给出记分板和团队迁移及模型文件。

记分牌模型

class Scoreboard < ActiveRecord::Base
  belongs_to :user
  has_many :teams, dependent: :destroy
  default_scope -> { order(created_at: :desc) }
end

记分板迁移

class CreateScoreboards < ActiveRecord::Migration
  def change
    create_table :scoreboards do |t|
      t.string :name_of_scoreboard
      t.string :name_of_organization
      t.string :name_of_activity
      t.references :user, index: true

      t.timestamps null: false
    end
    add_foreign_key :scoreboards, :users
    add_index :scoreboards, [:user_id, :created_at]
  end
end

团队模型

class Team < ActiveRecord::Base
  belongs_to :scoreboard
end

团队迁移

class CreateTeams < ActiveRecord::Migration
  def change
    create_table :teams do |t|
      t.string :name
      t.integer :win
      t.integer :loss
      t.integer :tie
      t.references :scoreboard, index:true

      t.timestamps null: false
    end
    add_foreign_key :teams, :scoreboards
  end
end

我想我已经正确关联了模型。因此,我的 Teams 控制器中用于创建操作的代码应该正确地创建关联。控制器如下:

记分牌控制器:

class ScoreboardsController < ApplicationController

 before_action :logged_in_user, only: [:new, :create, :show, :index]
 before_action :correct_user, only: [:destroy, :edit, :update]

 def new
   @scoreboard = Scoreboard.new
 end

 def create
  @scoreboard = current_user.scoreboards.build(scoreboard_params)
  if @scoreboard.save
   flash[:scoreboard] = "Scoreboard created successfully"
   redirect_to scoreboard_path(@scoreboard)
  else
   render 'new'
  end
 end

 def show
  @scoreboard = Scoreboard.find_by_id(params[:id])
 end

  def index
    if params[:search]
      @scoreboards = Scoreboard.all.search(params[:search])
    else
      @scoreboards = current_user.scoreboards
    end
  end

 def edit
  @scoreboard = Scoreboard.find_by_id(params[:id])
 end

 def update
  @scoreboard = Scoreboard.find_by_id(params[:id])
  if @scoreboard.update_attributes(scoreboard_params)
   flash[:success] = "Updated Successfully"
   redirect_to scoreboard_path(@scoreboard)
  else
   render 'edit'
  end
 end

 def destroy
  @scoreboard = Scoreboard.find_by_id(params[:id])
  @scoreboard.destroy
  flash[:success] = "Deleted Successfully."
  redirect_to scoreboards_path
 end

private

  def scoreboard_params
   params.require(:scoreboard).permit(:name_of_scoreboard, :name_of_organization, 
                  :name_of_activity, :starts_at, :ends_at, :cities, :states, :country, :picture ) 
  end

   def correct_user
     @user = Scoreboard.find(params[:id]).user
     redirect_to scoreboards_path unless current_user?(@user)
   end

end 

这是团队管理员:

class TeamsController < ApplicationController

def new
    @team = Team.new
  end

  def create
    @scoreboard= current_user.scoreboards.build
    @team = @scoreboards.teams.build(team_params)
    if @team.save
      flash[:success] = "Saved Successfully"
      redirect_to scoreboard_path
    else
      render 'new'
    end
  end

  def index

  end

  def show

  end

  private

  def team_params
    params.require(:team).permit(:name, :win, :loss, :tie)
  end

end

但是,当我提交应用创建操作的表单时出现错误 "undefined method `teams' for nil:NilClass"。我不确定为什么会这样,因为我对用户和记分牌模型做了完全相同的事情。

我针对您的问题修改了答案:

我认为您的联想是正确的,也许可以尝试使用@scoreboard 而不是记分牌。那么这样的事情可能吗?

def blah
    .... some code ....
    @scoreboard = current_user.scoreboards.build
    @team = @scoreboard.teams.build
    .... more code ....
end

Rails 中有多种方法可以通过用户输入创建关联记录。在这种情况下,我们假设您只想在记分牌的上下文中创建团队。

我们要做的第一件事是将团队路线嵌套在记分牌下:

# config/routes.rb
Rails.application.routes.draw do
  # ...
  resources :scoreboards do
    resources :teams, shallow: true
  end
end

这将为我们提供创建嵌套团队的途径:

             Prefix Verb   URI Pattern                                     Controller#Action
   scoreboard_teams GET    /scoreboards/:scoreboard_id/teams(.:format)     teams#index
                    POST   /scoreboards/:scoreboard_id/teams(.:format)     teams#create
new_scoreboard_team GET    /scoreboards/:scoreboard_id/teams/new(.:format) teams#new
          edit_team GET    /teams/:id/edit(.:format)                       teams#edit
               team GET    /teams/:id(.:format)                            teams#show
                    PATCH  /teams/:id(.:format)                            teams#update
                    PUT    /teams/:id(.:format)                            teams#update
                    DELETE /teams/:id(.:format)                            teams#destroy

为什么?,因为这提供了一个 RESTful 设计,很明显您正在创建嵌套资源。嵌套索引路由真的是可选的。

如果您想要一个显示所有球队的非嵌套索引,而不考虑记分牌,您可以这样定义路线:

# config/routes.rb
Rails.application.routes.draw do
  # ...
  resources :teams, only: :index
  resources :scoreboards do
    resources :teams, shallow: true, except: :index
  end
end

正如您已经猜到的,我们想在 Scoreboards#show

中构建一个 Team 实例
# GET /scoreboards/1
def show
  @team = @scoreboard.teams.build
end

我们还需要在计分板上添加一个团队表格。让我们为此使用可重用的部分:

# app/views/scoreboards/show.html.erb 
<%= render partial: 'teams/form' %>

# app/views/teams/_form.html.erb
<%= form_for(@team.new_record? ? [@scoreboard, @team] : @team ) do |f| %>
  <% if @team.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@team.errors.count, "error") %> prohibited this team from being saved:</h2>

      <ul>
      <% @team.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :wins %><br>
    <%= f.number_field :wins %>
  </div>
  <div class="field">
    <%= f.label :loss %><br>
    <%= f.number_field :loss %>
  </div>
  <div class="field">
    <%= f.label :tie %><br>
    <%= f.number_field :tie %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

通知@team.new_record? ? [@scoreboard, @team] : @team。我们使用三元运算符(紧凑的 if 语句),以便表单在新记录和编辑记录时都能正确路由。

所以在我们的 TeamsController 中,我们想要设置一个回调来从 params[:schoolboard_id] 加载记分牌,这样我们就可以在我们的新动作和创建动作中使用它。

class TeamsController < ApplicationController

  before_action :set_scoreboard, only: [:new, :create]
  before_action :set_team, only: [:show, :edit, :update, :destroy]

  # GET /scoreboard/:scoreboard_id/teams
  def index
    @scoreboard = Scoreboard.eager_load(:teams)
                            .find(params[:scoreboard_id])
    @teams = @scoreboard.teams
  end

  # GET /teams/1
  def show
  end

  # GET /scoreboard/:scoreboard_id/teams/new
  def new
    @team = @scoreboard.teams.new
  end

  # POST /scoreboard/:scoreboard_id/teams
  def create
    @team = @scoreboard.teams.new(team_params)

    if @team.save
      redirect_to @team, notice: 'Team was successfully created.'
    else
      render :new
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_team
      @team = Team.find(params[:id])
    end

    def set_scoreboard
      @scoreboard = Scoreboard.find(params[:scoreboard_id])
    end

    # Only allow a trusted parameter "white list" through.
    def team_params
      params.require(:team).permit(:name, :wins, :loss, :tie, :scoreboard_id)
    end
end