2009-08-15 15 views
11

पर सेटर ओवरराइड के साथ समस्या यह वास्तव में एक प्रश्न नहीं है, यह एक रिपोर्ट है कि जब मैं एक वस्तु है, तो पर गुण एक वस्तु है, तो मैंने एक समस्या हल की है। मुझे उम्मीद है कि यह एक ही समस्या का सामना करने वाले अन्य लोगों के लिए उपयोगी हो सकता है।ActiveRecord

मुझे एक उदाहरण के साथ स्पष्ट करने दें। मान लीजिए आप दो वर्गों, Book और Author है:

class Book < ActiveRecord::Base 
    belongs_to :author 
end 

class Author < ActiveRecord::Base 
    has_many :books 
end 

बहुत ही सरल। लेकिन, किसी भी कारण से, आपको author = Book पर विधि को ओवरराइड करने की आवश्यकता है। चूंकि मैं रेल के लिए नया हूं, मैंने रेल के साथ एग्इल वेब विकास पर सैम रूबी के सुझाव का पालन किया है: 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 

दुर्भाग्यवश, यह काम नहीं करता है। यही तो मैं कंसोल से प्राप्त होते हैं:

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

ऐसा लगता है कि रेल यह एक वस्तु है और कुछ भी नहीं करता है नहीं पहचानता है: attribuition के बाद, लेखक अभी भी नहीं के बराबर है! बेशक, मैं write_attribute(:author_id, author.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 

इस बार, सांत्वना था मेरे लिए अच्छा:

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

यहाँ चाल alias_method_chain, कि एक इंटरसेप्टर और पुराने सेटर (author_without_lookup) का एक वैकल्पिक नाम (इस मामले author_with_lookup में) बनाता है। मैं कबूल करता हूं कि इस व्यवस्था को समझने में कुछ समय लगा और मुझे खुशी होगी कि अगर कोई इसे विस्तार से समझाएगा, लेकिन मुझे आश्चर्य हुआ कि इस तरह की समस्या के बारे में जानकारी की कमी क्या थी। मुझे सिर्फ एक पोस्ट खोजने के लिए बहुत कुछ करना है, कि शीर्षक से शुरुआत में समस्या से असंबंधित लग रहा था। मैं रेल के लिए नया हूं, तो आप लोग क्या सोचते हैं: क्या यह एक बुरा अभ्यास है?

उत्तर

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 

फिर आप इसे एक फॉर्म फ़ील्ड पर लागू करने जैसी अच्छी चीजें कर सकते हैं।

<%= f.text_field :author_name %> 

क्या यह आपकी स्थिति के लिए काम करेगा?

+0

मैं इस बनाने के लिए सोचा था कि हल, लेकिन मैं डुप्लिकेट गुण नहीं चाहता था। मैंने जिस विधि का प्रस्ताव दिया है वह बहुत अच्छी तरह से काम कर रहा है; मैं बस इसे साझा करना चाहता था। मैं वास्तव में इसके साथ text_field चाल बना सकता हूं। लेकिन आपके उत्तर के लिए धन्यवाद! = डी –

+2

'author_name' विधि को 'प्रतिनिधि: नाम,: से =>: लेखक,: prefix => true' के साथ प्रतिस्थापित करने के लिए और अधिक सही नहीं होगा? –

+2

@ एडम, यह निश्चित रूप से ऐसा करने का एक वैकल्पिक तरीका है। कई तरीकों से निपटने के दौरान आमतौर पर मैं केवल 'प्रतिनिधि' का उपयोग करता हूं। यदि केवल एक ही है तो मैं विधि को सीधे परिभाषित करना पसंद करता हूं क्योंकि मुझे लगता है कि यह अधिक स्पष्ट है। – ryanb

6

जब आप एक्सेसर ओवरराइड, आप write_attribute और self[: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"> 

मैं दृढ़ता से अनुशंसा करता हूं कि रायन बेट्स क्या सुझाव देते हैं; एक नया author_name विशेषता बनाएं और एसोसिएशन जेनरेट की गई विधियों को छोड़ दें। कम फज़, कम भ्रम।

+0

जैसा कि मैंने उपरोक्त कहा है, आपके द्वारा प्रस्तावित विधि केवल तभी काम करती है जब लेखक सहेजता है (यानी एक आईडी है), जो मेरा मामला नहीं है। मेरे पास एक नया लेखक हो सकता है जिसे केवल सहेजा जाना चाहिए जब पुस्तक भी सहेजी जाती है। –

+0

मैंने आपकी टिप्पणी के आधार पर थोड़ा सा लिखा है। क्या अब यह और अधिक समझ में आता है? –

+0

ओह धन्यवाद, अब यह काम करता है जैसा कि मैं उम्मीद करता हूं। लेकिन (सभी सिफारिशों के बावजूद) मैं मूल समाधान रखूंगा। फिर भी, अगर मैं किसी दिन अपना मन बदलता हूं तो यह एक अच्छा विकल्प है। =] –

0

मैं इस समस्या का उपयोग कर alias_method

class Book < ActiveRecord::Base 
    belongs_to :author 

    alias_method :set_author, :author= 
    def author=(author) 
    author = Author.find_or_initialize_by_name(author) if author.is_a? String 
    set_author(author) 
    end 
end