Esto no es exactamente una pregunta, es más bien un informe sobre cómo resolví un problema con write_attribute
cuando el atributo es un objeto, en Rails 'Active Record
. Espero que esto pueda ser útil para otros que enfrentan el mismo problema.Problema con la anulación setter en ActiveRecord
Déjenme explicarlo con un ejemplo. Suponga que tiene dos clases, Book
y Author
:
class Book < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :books
end
muy simple. Pero, por alguna razón, debe anular el author
= método en Book
. Como soy nuevo en Rails, he seguido la sugerencia de Sam Ruby sobre Desarrollo web ágil con Rails: use el método privado attribute_writer
. Por lo tanto, mi primer intento fue:
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
Lamentablemente, esto no funciona. Eso es lo que me pasa desde la consola:
>> 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
Parece que los carriles no reconoce que es un objeto y no hace nada: después de la attribuition, autor sigue siendo nula! Por supuesto, podría probar write_attribute(:author_id, author.id)
, pero no ayuda cuando el autor aún no está guardado (¡aún no tiene una identificación!) Y necesito que los objetos se guarden juntos (el autor debe guardarse solo si el libro es válido).
Después de buscar mucho para una solución (y tratar muchas otras cosas en vano), me encontré con este mensaje: http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4fe057494c6e23e8, por lo que finalmente podría tenido algún código de trabajo:
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
Esta vez, la consola era agradable para mí:
>> 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>
el truco aquí es el alias_method_chain
, que crea un interceptor (en este caso author_with_lookup
) y un nombre alternativo a la edad colocador (author_without_lookup
). Confieso que me tomó un tiempo entender este arreglo y me alegraría que alguien se ocupe de explicarlo en detalle, pero lo que me sorprendió fue la falta de información sobre este tipo de problema. Tengo que buscar mucho en Google para encontrar una sola publicación, que por el título parecía inicialmente no relacionada con el problema. Soy nuevo en Rails, ¿qué opinas, chicos? ¿Es una mala práctica?
pensé que hacer esto, pero yo no quería atributos duplicados. El método que propuse arriba está funcionando muy bien; Solo quería compartirlo. De hecho, puedo hacer el truco text_field con él. ¡Pero gracias por tu respuesta! = D –
¿No sería más correcto reemplazar el método 'author_name' con' delegate: name,: to =>: author,: prefix => true'? –
@ Adam, esa es ciertamente una forma alternativa de hacerlo. Usualmente solo uso 'delegate' cuando trato con múltiples métodos. Si solo hay uno, prefiero definir el método directamente porque creo que es más claro. – ryanb