【问题标题】:Using Rails find_or_create_by with form objects使用带有表单对象的 Rails find_or_create_by
【发布时间】:2016-05-23 00:46:27
【问题描述】:

我正在创建一个 Rails 库应用程序,其中一个流派 has_many 书籍和一本书 belongs_to 一个流派。我需要使用表单对象,因为我的数据模型最终会有多个多对多关系,并且因为我想学习如何使用表单对象。我的表单对象基于 Rails Cast #416 表单对象。我的表单、表单对象、模型和控制器似乎都可以工作。新书被创建并且它们与流派相关联,但它们都是流派“流派”。我正在使用find_or_create_by。我认为问题在于book_form.rb,其中@genre ||= Genre.find_by(name: :name) 实际上并没有从表单中传递流派信息。我的相关代码如下:

模型书.rb

class Book < ActiveRecord::Base
  belongs_to :genre
  before_save :convert_isbn
.
.
.
end

模型流派.rb

class Genre < ActiveRecord::Base
  has_many :books, dependent: :destroy
  validates_associated :books
end

book_form.rb

class BookForm
  include ActiveModel::Model

  def self.model_name
    ActiveModel::Name.new(self, nil, "Book")
  end
.
.
.
  validates :name, presence: true

  delegate :name, to: :genre
  delegate :title, :year, :pages, :isbn, :summary, :volume_a, :volume_b, to: :book

  def genre
    @genre ||= Genre.find_by(name: :name)
  end

  def book
    @book ||= genre.books.build
  end

  def submit(params)
    genre.attributes = params.slice(:name).permit(:name)
    book.attributes = params.slice(:title, :year, :pages, :isbn, :summary, :volume_a,:volume_b).permit(:title, :year, :pages, :isbn, :summary, :volume_a,:volume_b)
    if valid?
      genre.save!
      book.save!
      true
    else
      false
    end
  end
end

books_controller.rb

class BooksController < ApplicationController
  before_action :admin_user, only: [:new, :edit, :destroy]

  def new
    @book_form = BookForm.new
    @genres = Genre.all
  end

  def create
    @book_form = BookForm.new
    @genres = Genre.all
    if @book_form.submit(params[:book])
      flash[:success] = "You've added a new book to the library!"
      redirect_to @book_form
    else
      render 'new'
    end
  end

查看new.html.erb

<%= form_for @book_form do |f| %>
  <%=render 'shared/error_messages', object: f.object %>

  <%= f.label :title %>
  <%= f.text_field :title, class: 'form-control' %>
  .
  .
  .
  <%= f.label :genre %><br>

  <%= collection_select(:genre, :name, Genre.all, :name, :name) %>
  <br>
  <br>
  <%= f.submit "Add book to library", class: "btn btn-primary" %>
<% end %>

使用 Pry gem,我在创建一本书时从服务器获取此信息:

Started POST "/books" for ::1 at 2016-02-22 14:19:20 -0700
Processing by BooksController#create as HTML
  Parameters: {"utf8"=>"✓",     "authenticity_token"=>"bPusgyl9n+p07eQsEAe9CpSsithtkg33HMifj8KTsidv3GDLuhjibOC7d2mm5boC4w7ZUne64R4n4OMQotDE4g==",
               "book"=>{"title"=>"test",
               "year"=>"2016",
               "pages"=>"222",
               "isbn"=>"9780-xx-xx-xx",
               "summary"=>"fake summary",
               "volume_a"=>"1",
               "volume_b"=>"2"},
               "genre"=>{"name"=>"Mystery"},
               "commit"=>"Add book to library"}

From: /home/nathaniel/rails_apps/allredlib/app/forms/book_form.rb @ line 38 BookForm#submit:

    37: def submit(params)
 => 38:   binding.pry
    39:   genre.attributes = params.slice(:name).permit(:name)
    40:   book.attributes = params.slice(:title, :year, :pages, :isbn, :summary, :volume_a,:volume_b).permit(:title, :year, :pages, :isbn, :summary, :volume_a,:volume_b)
    41:   if valid?
    42:     genre.save!
    43:     book.save!
    44:     true
    45:   else
    46:     false
    47:   end
    48: end

所以书籍类型正在参数中传递,但我在书籍形式中以错误的方式访问它。如果我注释掉 binding.pry,表单会创建一本新书,类型为“名称”,而不是我想要的类型“神秘”。

当我在使用 binding.pry 时将 @genre 输入到 rails 时,我得到了

[1] pry(#<BookForm>)> @genre
=> #<Genre:0x007f19554e1220
 id: 20,
 name: "name",
 book_id: nil,
 created_at: Thu, 25 Feb 2016 20:28:45 UTC +00:00,
 updated_at: Thu, 25 Feb 2016 20:28:45 UTC +00:00>

最新的 binding.pry 结果 27:定义类型 => 28:绑定.pry 29:@流派||=流派.find_or_initialize_by(名称::名称) 30: #@genre ||= Genre.find_or_initialize_by(name: params[:genre][:name]) 31:结束

【问题讨论】:

  • 我想您可能正在寻找这个? stackoverflow.com/questions/7537180/…
  • 是的,我想是的。我对 Rails 很陌生,不知道如何以正确的方式实施他们的解决方案。有什么指点吗?

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


【解决方案1】:

如果您要通过名称查找流派,则需要在方法的选项哈希中传入名称属性。

def genre
  @genre ||= Genre.find_or_create_by(name: :name)
end

更新

改变

genre.attributes = params.slice(:name).permit(:name)

genre.attributes = { :name => params[:genre][:name] }

同时在genre.save之前和之后抛出binding.pry!看看你输入@genre会得到什么,因为你的流派方法应该已经创建了你刚刚发送参数的实例变量genre.attributes接受哈希。

我会在 book 参数上考虑相同,但如果 slice 工作得很好。

此外,由于您在表单视图中调用 Genre.all,您可能不需要在控制器操作中执行此操作,但如果您这样做,请考虑在表单视图中使用对象 @genres @ 987654330@ 而是因为您在模型上发出了两次 all 请求。

@genres = Genre.all

【讨论】:

  • 这很有帮助。我在答案的底部添加了 Pry 的输出。正确的流派至少被传递到哈希中,所以看起来name: :name 来自@genre ||= Genre.find_or_create_by(name: :name) 是访问流派名称的错误方法。我无法使用@genre.name 或genre.name 访问它,这会导致错误或服务器崩溃。我同意我需要测试,但我对测试的了解还不够,无法编写有用的测试。
  • 我会看看这个。
  • 再次观看 Railscast 视频并尝试了解为什么要使用表单对象。 Rails 使用它的关联可以很好地开箱即用。如果您在属于关系中构建一个新的关联模型,我可以看到它很有用,也许是 Book has_one Author。但我认为你在这里增加了一层复杂性,这并没有多大意义。如果您执行 genre.attributes = params.slice(:genre) 并忽略其余部分会发生什么?
  • 这也可能对您有所帮助。我不知道validates_associated :books 发生了什么,但看起来你可以进行双重验证? api.rubyonrails.org/classes/ActiveRecord/Validations/…apidock.com/rails/ActiveRecord/Base/attributes%3D
  • 第一次调用流派方法时,它会创建一个@genre 实例,因为它在 "name" 的数据库中查找匹配项。运行 rails cGenre.all.find_by(name: 'name') 将返回第一个匹配项。执行Genre.where(name: 'name').count,它将告诉您数据库中有多少匹配项。您可以使用Genre.where(name: 'name').delete_all 将它们全部删除,但这就是问题所在。您需要使用@genre =|| Genre.find_or_initialize_by(name: params[:genre][:name])
【解决方案2】:

您正在创建流派,每次都将流派设置为流派。您不想create 一个流派,您可能只想实例化一个流派(使用new)。

尝试更改此设置

@genre ||= Genre.find_or_create_by(genre: :genre)

这样的事情

@genre ||= Genre.new

另外,我建议将模型流派上的字段“流派”重命名为“名称”之类的其他名称。否则,事情会变得非常混乱。

认为这一行:

collection_select(:genre, :id, Genre.all, :genre, :genre)

应该更像:

collection_select(:genre, :genre, Genre.all, :genre, :genre)

如果你要改成“名字”:

collection_select(:genre, :name, Genre.all, :name, :name)

【讨论】:

  • 确实如此。 @genre ||= Genre.new 的问题在于它不会找到或创建我的流派。它只创建一个新的流派,属性流派为 nil,属性流派_id 作为下一个 id 号。
  • 我添加了更多的 cmets。 find_or_create 的问题在于您使用它的方式,它会第一次创建流派“流派”,然后每次都会找到相同的流派,而您不会创建新流派。或者您只是更改旧名称。
  • 感谢您的帮助。我同意流派是一个令人困惑的列名,并且已经更改了它。我真正想做的是选择一个已经存在的类型,并将一本新书与它联系起来。我可以使用“defgenre @genre ||= Genre.find_by(name: :History) end”静态地执行此操作,但我需要用读取表单输入值的内容替换 :History。有什么想法吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-19
相关资源
最近更新 更多