Rails .build不是在建造has_many:选项

发布于 2025-01-21 17:33:20 字数 6486 浏览 3 评论 0原文

我有一个带有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

我希望该问题表格具有添加选项的字段,因此我将其添加到Question _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个POSSBILE选项,因此在Question_controller.rb中我添加了以下内容:

 def new
    @question = @poll.questions.build
    3.times { @question.options.build } # 3 different options 
  end

尽管如此,我只看到一个选项块而不是3个选项块。为什么是这样,我该如何解决? 此外,我没有看到“ postgresql”表中的新条目。


完整的问题_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

路由.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 %>

I have a Poll app with 3 models.

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

I want the question form to have a field for adding options so I've added this to the question _form.

  <%= form.fields_for :option do |o| %>
    <div>
      <%= o.label "Option", style: "display: block" %>
      <%= o.text_field :title, placeholder: "Enter Option here" %>
    </div>
  <% end %>

I can now see an option block which is good. Although I wish to have 3 possbile options so in the questions_controller.rb I've added the following:

 def new
    @question = @poll.questions.build
    3.times { @question.options.build } # 3 different options 
  end

Despite this I'm only seeing one option block instead of the 3. Why is this the case and how do i fix? Additionally I'm not seeing new entries into the options postgresql table.


Full 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

Edit:


Here is my questions form partial.

_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 %>

Here is the poll's show where I am rendering the forms.

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 %>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

孤独岁月 2025-01-28 17:33:20

您传递给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中的复数。这是使惯例超过构型为您服务而不是与您不利的重要组成部分。

但是,此代码还有很多其他问题。

  • 常数应始终是camelcase大写 Ruby中的大写 - 您需要将类Poll将更改为class poll并修复全部对班级的引用。这不仅仅是一种风格问题,因为口译员对以大写字母开始完全不同的是标识符。
  • 您没有正确嵌套。您有一条嵌套的路线,但是您仍将其视为控制器和docstrings中的非嵌套资源。
  • 您正在通过参数白名单中的父级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 prefix collection collection路由新的)是嵌套的。

并且您将控制器设置为:

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          

由于实例变量被自动透露,因此您只会得到意外的零而不是错误。您实际想要的是:

<%= form_with(model: [@poll, question] ) do |form| %>

The argument you pass to fields_for has to match the name of the assocation on the model:

<%= form.fields_for :options do |o| %>
  <div>
    <%= o.label "Option", style: "display: block" %>
    <%= o.text_field :title, placeholder: "Enter Option here" %>
  </div>
<% end %>

Pay very careful attention to plurization in Rails. Its a huge part of getting Convention over Configuration to work for you instead of against you.

However there are a quite a few other problems with this code.

  • Constants should always be CamelCase or UPPERCASE in Ruby - you need to change class poll to class Poll and fix all the references to the class. This isn't just a matter of style since the interpreter treats identifiers that start with an uppercase letter completely differently.
  • You're not nesting it properly. You have a nested route but you're still treating it like a non-nested resource in your controller and docstrings.
  • You're passing the parent id in your params whitelist. :poll_id and :question_id should not be whitelisted. Do not pass the parent id with a hidden input. The question id is assigned by Rails - you should not trust the user to pass it.
  • The option should not need a poll_id. Use an indirect has_one assocation to go up the tree. This could cause a edge case where a question and its options belong to different polls.

First lets fix the models:

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

Then I would recommend that you use shallow nesting

resource :polls do
  resources :questions, shallow: true
end

This creates the questions member routes (show, edit, delete) without the /polls/:poll_id prefix while the collection routes (index, create, new) are nested.

And that you set controller up as:

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

The key difference here is that you should look up the poll by the parameter in the URL for the nested routes and create the question off the poll instance (which sets poll_id).

Added:

You're not actually using the model you initialized in your controller. If you want to render the form from a completely different action you need to initialize the instance variable there:

class PollsController < ApplicationController

  def show
    @question = @poll.questions.new
    3.times { @question.options.new } # 5 different options ???
  end
  # ...
end
<%= render "questions/form", question: @question %>

And in your partial you have a sneaky little bug. Ruby is case sensitive so @poll and @Poll are actually different variables.

irb(main):049:0> @foo = "bar"                                                                                                                                                                                 => "bar"
irb(main):050:0> @Foo
=> nil          

Since instance variables are auto-vivified you're just get an unexpected nil instead of an error. What you actually want is:

<%= form_with(model: [@poll, question] ) do |form| %>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文