Rails .build 没有构建 has_many :options
Rails .build is not building has_many :options
我有一个包含 3 个模型的投票应用程序。
Poll.rb
class poll < ApplicationRecord
validates_presence_of :user, :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
Question.rb
class Question < ApplicationRecord
validates_presence_of :poll_id, :question_id, :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
Option.rb
class Option < ApplicationRecord
validates_presence_of :question_id, :title
belongs_to :question
belongs_to :poll
end
我希望问题表单有一个用于添加选项的字段,所以我已将其添加到问题 _form 中。
<%= form.fields_for :option do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
我现在可以看到一个很好的选项块。虽然我希望有 3 个可能的选项,所以在 questions_controller.rb 中我添加了以下内容:
def new
@question = @poll.questions.build
3.times { @question.options.build } # 3 different options
end
尽管如此,我只看到一个选项块而不是 3 个。为什么会这样,我该如何解决? 此外,我没有看到新条目选项 postgresql table.
完整 questions_controller.rb
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll
# GET /questions or /questions.json
def index
@questions = Question.all
end
# GET /questions/1 or /questions/1.json
def show
end
# GET /questions/new
def new
# @question = Question.new
@question = @poll.questions.build
3.times { @question.options.build } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /questions or /questions.json
def create
@question = Question.new(question_params)
respond_to do |format|
if @question.save
format.html { redirect_to polls_question_url(@question), notice: "Question was successfully created." }
format.json { render :show, status: :created, location: @question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if @question.update(question_params)
format.html { redirect_to polls_question_url(@question), notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: @question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/1.json
def destroy
poll_id = Question.find_by(params[:poll_id])
session[:return_to] ||= request.referer
@question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
@question = Question.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
params.require(:question).permit(:poll_id, :question_type, :title, :description, :randomize_selection, :voter_abstain, { option_attributes: [:question_id, :poll_id, :party_id, :title, :description] } )
end
def set_poll
@poll = poll.find_by(params[:poll_id])
end
end
routes.rb
resources :users do
resources :polls
end
resource :polls do
resources :questions
end
resource :questions do
resources :options
end
编辑:
这是我的部分问题。
_form.html.erb
<%= form_with(model: [@Poll, question] ) do |form| %>
<% if question.errors.any? %>
<div style="color: red">
<h2><%= pluralize(question.errors.count, "error") %> prohibited this question from being saved:</h2>
<ul>
<% question.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.hidden_field :poll_id %>
</div>
<div>
<%= form.label :question_type, style: "display: block" %>
<%= form.text_field :question_type %>
</div>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description %>
</div>
<div>
<%= form.label :randomize_selection, style: "display: block" %>
<%= form.check_box :randomize_selection %>
</div>
<div>
<%= form.label :voter_abstain, style: "display: block" %>
<%= form.check_box :voter_abstain %>
</div>
<div>
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
这是我渲染表格的民意调查显示。
show.html.erb
<p style="color: green"><%= notice %></p>
<p>
<strong>Poll Title:</strong>
<%= @poll.title %>
<%= render @poll %>
</p>
<div>
<%= link_to "Edit this poll", edit_user_poll_path(@poll) %> |
<%= link_to "Back to polls", user_polls_path %> |
<%= link_to "Destroy this poll", user_poll_path(@poll), method: :delete %>
</div>
<% if @poll.questions.any? %>
<hr>
<h2>Questions:</h2>
<%= render @poll.questions %>
<% end %>
<hr>
<h2>Add a new Question:</h2>
<%= render "questions/form", question: @poll.questions.build %>
您传递给 fields_for
的参数必须与模型上的关联名称相匹配:
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
请特别注意 Rails 中的复数化。这是让 Convention over Configuration 为您工作而不是对您不利的重要组成部分。
但是这段代码还有很多其他问题。
- 常量应始终为 Ruby 中的
CamelCase
或 UPPERCASE
- 您需要将 class poll
更改为 class Poll
并修复对 class。这不仅仅是样式问题,因为解释器会完全不同地处理以大写字母开头的标识符。
- 你没有正确嵌套它。您有一个嵌套路由,但您仍然将其视为控制器和文档字符串中的 non-nested 资源。
- 您正在传递参数白名单中的父 ID。
:poll_id
和 :question_id
不应列入白名单。不要传递带有隐藏输入的父 ID。问题 ID 由 Rails 分配 - 你不应该相信用户会传递它。
- 该选项不需要
poll_id
。使用间接 has_one
关联向上树。这可能会导致问题及其选项属于不同民意调查的边缘情况。
首先让我们修复模型:
class Poll < ApplicationRecord
# belongs_to assocations are required by default
# adding validations will just cause duplicate error messages
validates_presence_of :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
class Question < ApplicationRecord
validates_presence_of :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
class Option < ApplicationRecord
validates_presence_of :title
belongs_to :question
has_one :poll, through: :question
end
那我推荐你用shallow nesting
resource :polls do
resources :questions, shallow: true
end
这会创建没有 /polls/:poll_id
前缀的问题 member routes(显示、编辑、删除),而 collection routes (index, create, new) 是嵌套的。
并且您将控制器设置为:
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll, only: %i[ new create index ]
# GET /polls/1/questions or /polls/1/questions.json
def index
@questions = @poll.questions.all
end
# GET /questions/1 or /polls/1/questions/1.json
def show
end
# GET /polls/1/questions/new
def new
# build is just an alias of new for legacy compatibility with Rails 2...
# its about time that we ditch it
@question = @poll.questions.new
3.times { @question.options.new } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /polls/1/questions or /polls/1/questions.json
def create
@question = @poll.questions.new(question_params)
respond_to do |format|
if @question.save
format.html { redirect_to @question, notice: "Question was successfully created." }
format.json { render :show, status: :created, location: @question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if @question.update(question_params)
format.html { redirect_to @question, notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: @question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/2.json
def destroy
session[:return_to] ||= request.referer
@question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
@question = Questions.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
# do not write this in a single unreadable line
params.require(:question).permit(
:question_type,
:title,
:description,
:randomize_selection,
:voter_abstain,
# do not wrap hash arguments in brackets
# as it will break if/when the `permit` method is changed to use real keyword arguments
# for has_many assocations the key naming convention is also plural_attributes
options_attributes: [
:party_id,
:title,
:description
]
)
end
def set_poll
@poll = Poll.find_by(params[:poll_id])
end
end
这里的主要区别在于,您应该通过 URL 中的参数查找嵌套路由的轮询,并根据轮询实例(设置 poll_id
)创建问题。
已添加:
您实际上并没有使用您在控制器中初始化的模型。如果你想从一个完全不同的动作中呈现表单,你需要在那里初始化实例变量:
class PollsController < ApplicationController
def show
@question = @poll.questions.new
3.times { @question.options.new } # 5 different options ???
end
# ...
end
<%= render "questions/form", question: @question %>
在你的部分,你有一个偷偷摸摸的小错误。 Ruby区分大小写,所以@poll
和@Poll
实际上是不同的变量。
irb(main):049:0> @foo = "bar" => "bar"
irb(main):050:0> @Foo
=> nil
因为实例变量是 auto-vivified 你只是得到一个意外的 nil 而不是错误。你真正想要的是:
<%= form_with(model: [@poll, question] ) do |form| %>
我有一个包含 3 个模型的投票应用程序。
Poll.rb
class poll < ApplicationRecord
validates_presence_of :user, :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
Question.rb
class Question < ApplicationRecord
validates_presence_of :poll_id, :question_id, :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
Option.rb
class Option < ApplicationRecord
validates_presence_of :question_id, :title
belongs_to :question
belongs_to :poll
end
我希望问题表单有一个用于添加选项的字段,所以我已将其添加到问题 _form 中。
<%= form.fields_for :option do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
我现在可以看到一个很好的选项块。虽然我希望有 3 个可能的选项,所以在 questions_controller.rb 中我添加了以下内容:
def new
@question = @poll.questions.build
3.times { @question.options.build } # 3 different options
end
尽管如此,我只看到一个选项块而不是 3 个。为什么会这样,我该如何解决? 此外,我没有看到新条目选项 postgresql table.
完整 questions_controller.rb
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll
# GET /questions or /questions.json
def index
@questions = Question.all
end
# GET /questions/1 or /questions/1.json
def show
end
# GET /questions/new
def new
# @question = Question.new
@question = @poll.questions.build
3.times { @question.options.build } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /questions or /questions.json
def create
@question = Question.new(question_params)
respond_to do |format|
if @question.save
format.html { redirect_to polls_question_url(@question), notice: "Question was successfully created." }
format.json { render :show, status: :created, location: @question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if @question.update(question_params)
format.html { redirect_to polls_question_url(@question), notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: @question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/1.json
def destroy
poll_id = Question.find_by(params[:poll_id])
session[:return_to] ||= request.referer
@question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
@question = Question.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
params.require(:question).permit(:poll_id, :question_type, :title, :description, :randomize_selection, :voter_abstain, { option_attributes: [:question_id, :poll_id, :party_id, :title, :description] } )
end
def set_poll
@poll = poll.find_by(params[:poll_id])
end
end
routes.rb
resources :users do
resources :polls
end
resource :polls do
resources :questions
end
resource :questions do
resources :options
end
编辑:
这是我的部分问题。
_form.html.erb
<%= form_with(model: [@Poll, question] ) do |form| %>
<% if question.errors.any? %>
<div style="color: red">
<h2><%= pluralize(question.errors.count, "error") %> prohibited this question from being saved:</h2>
<ul>
<% question.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.hidden_field :poll_id %>
</div>
<div>
<%= form.label :question_type, style: "display: block" %>
<%= form.text_field :question_type %>
</div>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description %>
</div>
<div>
<%= form.label :randomize_selection, style: "display: block" %>
<%= form.check_box :randomize_selection %>
</div>
<div>
<%= form.label :voter_abstain, style: "display: block" %>
<%= form.check_box :voter_abstain %>
</div>
<div>
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
这是我渲染表格的民意调查显示。
show.html.erb
<p style="color: green"><%= notice %></p>
<p>
<strong>Poll Title:</strong>
<%= @poll.title %>
<%= render @poll %>
</p>
<div>
<%= link_to "Edit this poll", edit_user_poll_path(@poll) %> |
<%= link_to "Back to polls", user_polls_path %> |
<%= link_to "Destroy this poll", user_poll_path(@poll), method: :delete %>
</div>
<% if @poll.questions.any? %>
<hr>
<h2>Questions:</h2>
<%= render @poll.questions %>
<% end %>
<hr>
<h2>Add a new Question:</h2>
<%= render "questions/form", question: @poll.questions.build %>
您传递给 fields_for
的参数必须与模型上的关联名称相匹配:
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
请特别注意 Rails 中的复数化。这是让 Convention over Configuration 为您工作而不是对您不利的重要组成部分。
但是这段代码还有很多其他问题。
- 常量应始终为 Ruby 中的
CamelCase
或UPPERCASE
- 您需要将class poll
更改为class Poll
并修复对 class。这不仅仅是样式问题,因为解释器会完全不同地处理以大写字母开头的标识符。 - 你没有正确嵌套它。您有一个嵌套路由,但您仍然将其视为控制器和文档字符串中的 non-nested 资源。
- 您正在传递参数白名单中的父 ID。
:poll_id
和:question_id
不应列入白名单。不要传递带有隐藏输入的父 ID。问题 ID 由 Rails 分配 - 你不应该相信用户会传递它。 - 该选项不需要
poll_id
。使用间接has_one
关联向上树。这可能会导致问题及其选项属于不同民意调查的边缘情况。
首先让我们修复模型:
class Poll < ApplicationRecord
# belongs_to assocations are required by default
# adding validations will just cause duplicate error messages
validates_presence_of :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
class Question < ApplicationRecord
validates_presence_of :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
class Option < ApplicationRecord
validates_presence_of :title
belongs_to :question
has_one :poll, through: :question
end
那我推荐你用shallow nesting
resource :polls do
resources :questions, shallow: true
end
这会创建没有 /polls/:poll_id
前缀的问题 member routes(显示、编辑、删除),而 collection routes (index, create, new) 是嵌套的。
并且您将控制器设置为:
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll, only: %i[ new create index ]
# GET /polls/1/questions or /polls/1/questions.json
def index
@questions = @poll.questions.all
end
# GET /questions/1 or /polls/1/questions/1.json
def show
end
# GET /polls/1/questions/new
def new
# build is just an alias of new for legacy compatibility with Rails 2...
# its about time that we ditch it
@question = @poll.questions.new
3.times { @question.options.new } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /polls/1/questions or /polls/1/questions.json
def create
@question = @poll.questions.new(question_params)
respond_to do |format|
if @question.save
format.html { redirect_to @question, notice: "Question was successfully created." }
format.json { render :show, status: :created, location: @question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if @question.update(question_params)
format.html { redirect_to @question, notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: @question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/2.json
def destroy
session[:return_to] ||= request.referer
@question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
@question = Questions.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
# do not write this in a single unreadable line
params.require(:question).permit(
:question_type,
:title,
:description,
:randomize_selection,
:voter_abstain,
# do not wrap hash arguments in brackets
# as it will break if/when the `permit` method is changed to use real keyword arguments
# for has_many assocations the key naming convention is also plural_attributes
options_attributes: [
:party_id,
:title,
:description
]
)
end
def set_poll
@poll = Poll.find_by(params[:poll_id])
end
end
这里的主要区别在于,您应该通过 URL 中的参数查找嵌套路由的轮询,并根据轮询实例(设置 poll_id
)创建问题。
已添加:
您实际上并没有使用您在控制器中初始化的模型。如果你想从一个完全不同的动作中呈现表单,你需要在那里初始化实例变量:
class PollsController < ApplicationController
def show
@question = @poll.questions.new
3.times { @question.options.new } # 5 different options ???
end
# ...
end
<%= render "questions/form", question: @question %>
在你的部分,你有一个偷偷摸摸的小错误。 Ruby区分大小写,所以@poll
和@Poll
实际上是不同的变量。
irb(main):049:0> @foo = "bar" => "bar"
irb(main):050:0> @Foo
=> nil
因为实例变量是 auto-vivified 你只是得到一个意外的 nil 而不是错误。你真正想要的是:
<%= form_with(model: [@poll, question] ) do |form| %>