在ActiveRecord上使用setter覆盖问题

Lai*_*ira 11 activerecord ruby-on-rails

这不是一个问题,而是一个关于我如何write_attribute在Rails 上解决属性是一个对象的问题的报告Active Record.我希望这对面临同样问题的其他人有用.

让我举个例子来解释一下.假设您有两个类,Book并且Author:

class Book < ActiveRecord::Base
  belongs_to :author
end

class Author < ActiveRecord::Base
  has_many :books
end
Run Code Online (Sandbox Code Playgroud)

非常简单.但是,无论出于何种原因,您需要覆盖author=方法Book.由于我是Rails的新手,我遵循了Sam Ruby关于使用Rails进行敏捷Web开发的建议:使用attribute_writer私有方法.所以,我的第一次尝试是:

class Book < ActiveRecord::Base
  belongs_to :author

  def author=(author)
    author = Author.find_or_initialize_by_name(author) if author.is_a? String
    self.write_attribute(:author, author)
  end
end
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不起作用.这就是我从控制台得到的:

>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author = "Lewis Carroll"
=> "Lewis Carroll"
>> book
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> nil
Run Code Online (Sandbox Code Playgroud)

似乎Rails不承认它是一个对象并且没有做任何事情:在归属之后,作者仍然是零!当然,我可以尝试write_attribute(:author_id, author.id),但是当作者还没有保存时​​它没有帮助(它仍然没有id!)并且我需要将对象保存在一起(只有在书籍有效时才必须保存作者).

在搜索了很多解决方案之后(并尝试了许多其他事情都是徒劳的),我发现了这条消息:http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4fe057494c6e23e8,所以最后我可以得到一些工作代码:

class Book < ActiveRecord::Base
  belongs_to :author

  def author_with_lookup=(author)
    author = Author.find_or_initialize_by_name(author) if author.is_a? String
    self.author_without_lookup = author
  end
  alias_method_chain :author=, :lookup
end
Run Code Online (Sandbox Code Playgroud)

这一次,控制台对我很好:

>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author = "Lewis Carroll"=> "Lewis Carroll"
>> book
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> #<Author id: nil, name: "Lewis Carroll", created_at: nil, updated_at: nil>
Run Code Online (Sandbox Code Playgroud)

这里的技巧是alias_method_chain,创建一个拦截器(在这种情况下author_with_lookup)和旧setter(author_without_lookup)的替代名称.我承认花了一些时间来理解这种安排,如果有人愿意详细解释,我会很高兴,但让我感到惊讶的是缺乏关于这类问题的信息.我必须谷歌很多才能找到一个帖子,标题似乎最初与问题无关.我是Rails的新手,所以你觉得伙计们:这是一个不好的做法吗?

rya*_*anb 20

我建议创建一个虚拟属性,而不是覆盖该author=方法.

class Book < ActiveRecord::Base
  belongs_to :author

  def author_name=(author_name)
    self.author = Author.find_or_initialize_by_name(author_name)
  end

  def author_name
    author.name if author
  end
end
Run Code Online (Sandbox Code Playgroud)

然后你可以做一些很酷的事情,比如将它应用到表单字段.

<%= f.text_field :author_name %>
Run Code Online (Sandbox Code Playgroud)

这适合你的情况吗?

  • 用`delegate:name,:to =>:author,:prefix => true`替换`author_name`方法不是更正确吗? (2认同)
  • @Adam,这当然是另一种方法.在处理多种方法时,我通常只使用`delegate`.如果只有一个我更喜欢直接定义方法,因为我觉得它更清楚. (2认同)

Aug*_*aas 6

当你重写访问,你必须设置一个实际的数据库属性的write_attributeself[:the_attribute]=,而不是你覆盖的关联产生的属性的名称.这适合我.

require 'rubygems'
require 'active_record'
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
ActiveRecord::Schema.define do
  create_table(:books) {|t| t.string :title }
  create_table(:authors) {|t| t.string :name }
end

class Book < ActiveRecord::Base
  belongs_to :author

  def author=(author_name)
    found_author = Author.find_by_name(author_name)
    if found_author
      self[:author_id] = found_author.id
    else
      build_author(:name => author_name)
    end
  end
end

class Author < ActiveRecord::Base
end

Author.create!(:name => "John Doe")
Author.create!(:name => "Tolkien")

b1 = Book.new(:author => "John Doe")
p b1.author
# => #<Author id: 1, name: "John Doe">

b2 = Book.new(:author => "Noone")
p b2.author
# => #<Author id: nil, name: "Noone">
b2.save
p b2.author
# => #<Author id: 3, name: "Noone">
Run Code Online (Sandbox Code Playgroud)

我强烈建议做Ryan Bates建议的事情; 创建一个新author_name属性,并保持关联生成的方法不变.减少模糊,减少混乱.