【问题标题】:Create and push an association at the same time同时创建和推送关联
【发布时间】:2015-05-30 11:58:35
【问题描述】:

我有一个 Word 模型和一个 Category 模型。

单词 has_and_belongs_to_many 类别。 类别 has_and_belongs_to_many Words。

现在,我已经在控制台中设置并运行了所有内容,例如,您可以这样做:

Word.create(title: "Testicles")
testicles = Word.first

Category.create(title: "Genitalia")
genitalia = Category.first

testicles.categories << genitalia

testicles.categories
=> "genitalia"

现在我也可以使用视图中的表单来启动和运行它,但前提是我已经在单独的页面上以自己的表单单独创建了类别,并且与 Word 相同。然后在 Word 显示视图中,我可以创建一个表单来为其分配类别。

但是...我真正想做的是在创建 Word 的同时完成所有这些操作,即在“新单词”视图上。

我在解决如何做到这一点时遇到了很大的问题。我认为我说得对,在那个视图中我只能有一个表单和一个提交,所以我认为我必须以某种方式将所有内容从该表单发送到 WordsController,然后工作那里有一些魔法,但在这里做什么让我很头疼。有人可以帮忙吗?

我尚未创建用户模型或设置身份验证,因此在这方面没有障碍。

模型/word.rb:

class Word < ActiveRecord::Base
  belongs_to :user

  has_and_belongs_to_many :categories

  validates :title, presence: true
end

模型/类别.rb:

class Category < ActiveRecord::Base
    has_and_belongs_to_many :words

    validates :title, presence: true
end

schema.rb:

ActiveRecord::Schema.define(version: 20150529144121) do

  create_table "categories", force: :cascade do |t|
    t.string   "title"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "categories_words", id: false, force: :cascade do |t|
    t.integer "category_id"
    t.integer "word_id"
  end

  add_index "categories_words", ["category_id"], name: "index_categories_words_on_category_id"
  add_index "categories_words", ["word_id"], name: "index_categories_words_on_word_id"

  create_table "words", force: :cascade do |t|
    t.string   "title"
    t.text     "description"
    t.integer  "user_id"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  add_index "words", ["user_id"], name: "index_words_on_user_id"

end

在尝试了各种 form_tag 魔法(并且失败得很惨)之后,目前我正在使用这个表单:

<%= form_for(@word) do |f| %>
  <% if @word.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@word.errors.count, "error") %> prohibited this word from being saved:</h2>

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

  <div class="field">
    <%= f.label :title, 'Word' %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :description, 'Definition' %><br>
    <%= f.text_area :description %>
  </div>
  <div class="field">
    <%= f.label :categories, 'Category' %><br>
    <%= f.text_field :categories %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

在视图中,表单有:

#<Category::ActiveRecord_Associations_CollectionProxy:0x007f16dd834820>

在“类别”框中,但可以将其清除并输入您自己的类别。提交填写了所有字段的表单时,我收到 NoMethodError。

【问题讨论】:

  • 查找accepts_nested_attributes --

标签: ruby-on-rails ruby forms ruby-on-rails-4


【解决方案1】:

所以基本上你想同时创建一个词和一个类别,并将它们链接起来。如果经典解决方案(例如使用nested_form gem)不能完全满足您的需求,您可以尝试这种方法。

您确实需要一个自定义视图/控制器,并按照您的建议在您的控制器中开展业务。我建议您改为使用简单的form_tag,您可以在其中添加一些字段集。

查看

<%= form_tag your_custom_route_path, :html => {:class => "form-horizontal"} do |form| %> 
    <%= fields_for :word, @word do |f| %>
      <%= f.text_field :title %>
    <% end %>

    <%= fields_for :category, @category do |f| %>
      <%= f.text_field :title %>
    <% end %>
<% end %>

路线

post '/yourRoute', to: 'your_controller#your_action_create', as: 'your_custom_route'

具有一些动作的控制器

class YourController < ApplicationController

  # new
  def your_action_new
    @word = Word.new
    @category = Category.new
  end

  # Post
  def your_action_create
    @word = Word.new(word_params)
    @category = Category.new(category_params)
    if @word.save and @category.save
      @word.categories << @category
      @word.save 
    end
  end

  private

  def words_params
    params.require(:word).permit(:title, ...)
  end

  def category_params
    params.require(:category).permit(:title...)
  end

【讨论】:

    【解决方案2】:

    感谢 Cyril DD 的帮助,我对此进行了扩展并创建了一个表单和自定义控制器,它将创建一个新的 Word,并为其分配一个新的类别和/或用户可以将现有的类别分配给该新 Word。

    _form.html.erb:

    <%= form_tag(controller: "new_word", action: "create_word_and_category", method: "post") do %>
    
      <% if @word.errors.any? %>
        <div id="error_explanation">
          <h2><%= pluralize(@word.errors.count, "error") %> prohibited this category from being saved:</h2>
    
          <ul>
          <% @word.errors.full_messages.each do |message| %>
            <li><%= message %></li>
          <% end %>
          </ul>
        </div>
      <% end %>
    
      <%= fields_for :word, @word do |word_form| %>
        <div class="field">
          <%= word_form.label(:title, "Word:") %><br>
          <%= word_form.text_field(:title, id: "new_word", required: true) %>
        </div>
    
        <div class="field">
          <%= word_form.label(:description, "Definition:") %><br>
          <%= word_form.text_area(:description) %>
        </div>
      <% end %>
    
    
      <%= fields_for :category, @category do |category_form| %>
        <% if Category.count > 0 %>
          <div class="field">
            <%= category_form.label(:title, "Choose from existing Categories:") %><br>
            <%= category_form.collection_check_boxes(:category_ids, Category.all, :id, :title) %>
          </div>
        <% end %>
    
        <h4>AND/OR...</h4>
    
        <div class="field">
          <%= category_form.label(:title, "Create and Use a New Category:") %><br>
          <%= category_form.text_field(:title) %>
        </div>
      <% end %>
    
      <div class="actions">
        <%= submit_tag("Create") %>
      </div>
    <% end %>
    

    new_word_controller.rb(它还没有完全完成,但它'按照它在锡上说的那样'):

    class NewWordController < ApplicationController
    
        def create_word_and_category
            @word = Word.new(word_params)
    
            if @word.save
                (params["category"])["category_ids"].each do |i|
                    next if i.to_i == 0
                    @word.categories << Category.find(i.to_i) unless @word.categories.include?(Category.find(i.to_i))
                end
    
                if category_params.include?(:title) && ((params["category"])["title"]) != ""
                    @word.categories << Category.new(title: (params["category"])["title"])
                end
            end
    
            if @word.save
                redirect_to words_path, notice: 'Word was successfully created.'
            else
                redirect_to new_word_path, notice: 'A valid word was not submitted.'
            end
        end
    
        private
        # Use callbacks to share common setup or constraints between actions.
        # def set_word
        #   @word = Word.find(params[:id])
        # end
    
        # Never trust parameters from the scary internet, only allow the white list through.
        def word_params
          params.require(:word).permit(:title, :description, :user_id)
        end
    
        def category_params
            params.require(:category).permit(:title, :category_ids, :category_id)
        end
    
    end
    

    在我的 routes.rb 中:

    post 'create_word_and_category' => 'new_word#create_word_and_category'
    

    我不得不将该控制器代码放入旧的 words_controller 中,因为我认为没有必要将它放在像这样的单独控制器中。如果有人有任何想法/批评,那就太好了。如果有人可以帮我为这个控制器代码编写一个 rspec 控制器测试,那就更好了!我遇到了麻烦。

    【讨论】:

      猜你喜欢
      • 2018-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多