在 Rails 5 中,如何使用单个控制器操作在单独的模型中创建记录?

In Rails 5 how can I create records in separate models with a single controller action?

我正在学习 Rails 创建詹姆斯·邦德电影数据库的教程。我有两个模型,movie.rb 和 theme_song.rb。两者有如下关联:

class Movie < ApplicationRecord
  has_one :theme_song
end
class ThemeSong < ApplicationRecord
  belongs_to :movie
end

我也在我的 routes.rb 文件中代表了这个协会:

resources :movies do
  resources :theme_songs
end

我需要允许用户将其他电影的详细信息添加到数据库中,包括一般电影信息和有关主题曲的信息。一般信息属于Movie模型,主题曲信息属于ThemeSong模型,如下迁移所示:

create_table :movies do |t|
  t.string :title
  t.datetime :release_date
  t.text :plot
  t.timestamps
end
create_table :theme_songs do |t|
  t.string :song
  t.string :artist
  t.references :movie, foreign_key: true
  t.timestamps
end

我有一个 movies_controller.rb 控制器和一个链接到控制器的 new.html.erb 视图。 new.html.erb 视图中编码了以下形式:

<%= form_with model: @movie, local: true do |form| %>
  <p><strong>Movie Details</strong></p>
  <p>
    <%= form.label 'Title:' %>
    <%= form.text_field :title %>
  </p>
  <p>
    <%= form.label 'Release date:' %>
    <%= form.text_field :release_date %>
  </p>
  <p>
    <%= form.label 'Plot:' %>
    <%= form.text_area :plot %>
  </p>
  <p><strong>Soundtrack</strong></p>
  <p>
    <%= form.label 'Theme song:' %>
    <%= form.text_area :song %>
  </p>
  <p>
    <%= form.label 'Artist:' %>
    <%= form.text_area :artist %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

我需要用户能够填写该表单并提交,然后我的控制器创建操作以在 Movie 中添加一条包含一般信息的新记录,在 ThemeSong 中添加一条包含配乐信息的新记录,同时关联中链接的记录。

这是我目前在 movies_controller.rb:

的相关方法中得到的代码
def create
  @movie = Movie.new(movie_params)
  if @movie.save
    redirect_to @movie
  else
    render 'new'
  end
end

private

def movie_params
  params.require(:movie).permit(:title, :release_date, :plot, :song, :artist)
end

谁能告诉我 create/movie_params 方法中需要包含哪些代码?或者,如果我以完全错误的方式进行处理,如果是这样,我应该如何实施?

我正在使用 Rails 5.1.4

您需要使用的是 accepts_nested_attributes_for,它允许您在 movie 表单中为 theme_song 嵌套表单:

将其添加到您的 Movie 型号:

class Movie < ApplicationRecord
  has_one :theme_song

  accepts_nested_attributes_for :theme_song
end

在你的电影控制器中:

def new
  @movie = Movie.new
  @movie.build_theme_song
end

private

def movie_params
  params.require(:movie).permit(:title, :release_date, :plot, :song, :artist, theme_song_attributes: [:song, :artist])
end

最后,在您的 movie 表单中使用 fields_for 帮助程序为电影的 theme_song:

添加字段
<%= form_for @movie do |f| %>
  theme song:
  <ul>
  <%= f.fields_for @movie.theme_song do |theme_song_form| %>
    <li>
    <%= theme_song_form.label :song %>
    <%= theme_song_form.text_field :song %>
    ...
    </li>
  <% end %>
 </ul>
<% end %>

现在,当您提交表单时,它将为该电影创建 movie 记录和 theme_song 记录。

没有为 theme_song 模型提供信息,需要创建哪些字段或哪些数据,但您可能不希望两个表中的数据重复。

您没有走错路,使用 accepts_nested_attributes 将两组数据添加到单个表单可能是最简单的方法。然后告诉您的 movies_controller 参数也接受嵌套特征。然后以相应的方式保存数据是rails魔术。

1 为电影模型添加嵌套选项

class Movie < ApplicationRecord
  has_one :theme_song 
  accepts_nested_attributes_for :theme_song
end

2 将表格嵌套在一起

<%= form_with model: @movie, local: true do |form| %>
  <p><strong>Movie Details</strong></p>
  <p>
    <%= form.label 'Title:' %>
    <%= form.text_field :title %>
  </p>
  <p>
    <%= form.label 'Release date:' %>
    <%= form.text_field :release_date %>
  </p>
  <p>
    <%= form.label 'Plot:' %>
    <%= form.text_area :plot %>
  </p>
  <p><strong>Soundtrack</strong></p>
  <%= form.fields_for :theme_song do |theme_song| %>
    <p>
      <%= theme_song.label 'Theme song:' %>
      <%= theme_song.text_area :song %>
    </p>
    <p>
      <%= theme_song.label 'Artist:' %>
      <%= theme_song.text_area :artist %>
    </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

3 更新控制器参数 + 创建新对象

def create
  @movie = Movie.new(movie_params)
  if @movie.save
    redirect_to @movie
  else
    render 'new'
  end
end

def new
  @movie = Movie.new
  @movie.build_theme_song
end
private

def movie_params
  params.require(:movie).permit(:title, :release_date, :plot, :song, :artist, theme_song_attributes: [:song, :artist, :id])
end

这里有两点需要注意,第一是在新动作中你需要确保构建嵌套记录。其次嵌套的属性以数组的形式添加到params中,必须这样。

旁注

使用此方法有其局限性。你应该知道,这意味着如果两部不同的电影使用同一首主题曲,你将有 2 个电影记录和 2 个主题曲记录。这可能会使 slower/harder 变成 "show me all the movies that this song is in",因为这样你就必须考虑用户在按名称搜索时的错误和拼写。你可能会发现,在未来,最好有一个 Movie 记录,一个 Theme Song 记录和一个 MovieThemeSongs 记录,它本质上是将两者结合在一起。