2013-02-03 13 views
10

ब्रायन हेल्मकैम्प के उत्कृष्ट ब्लॉग पोस्ट में "7 Patterns to Refactor Fat ActiveRecord Models" कहा जाता है, वह Form Objects का उपयोग करके बहु-परत रूपों को दूर करने और accepts_nested_attributes_for का उपयोग करना बंद कर देता है।, मैं विशिष्टता की जांच कैसे करूं?

संपादित करें: समाधान के लिए below देखें।

मैं लगभग ठीक उसके कोड नमूना दोहराया गया है, जैसा कि मैंने हल करने के लिए एक ही समस्या थी:

class Signup 
    include Virtus 

    extend ActiveModel::Naming 
    include ActiveModel::Conversion 
    include ActiveModel::Validations 

    attr_reader :user 
    attr_reader :account 

    attribute :name, String 
    attribute :account_name, String 
    attribute :email, String 

    validates :email, presence: true 
    validates :account_name, 
    uniqueness: { case_sensitive: false }, 
    length: 3..40, 
    format: { with: /^([a-z0-9\-]+)$/i } 

    # Forms are never themselves persisted 
    def persisted? 
    false 
    end 

    def save 
    if valid? 
     persist! 
     true 
    else 
     false 
    end 
    end 

private 

    def persist! 
    @account = Account.create!(name: account_name) 
    @user = @account.users.create!(name: name, email: email) 
    end 
end 

बातें कोड के अपने टुकड़ा में अलग से एक है कि मैं मान्य करने के लिए की जरूरत है खाता नाम (और उपयोगकर्ता ई-मेल) की विशिष्टता। हालांकि, ActiveModel::Validations में uniqueness सत्यापनकर्ता नहीं है, क्योंकि यह ActiveRecord का एक गैर-डेटाबेस समर्थित संस्करण माना जाता है।

मैं लगा तीन तरीकों इस संभाल करने के लिए कर रहे हैं:

  • अपने ही विधि लिखें इस (बेमानी लगता है)
  • ActiveRecord :: सत्यापन :: UniquenessValidator शामिल जाँच करने के लिए (यह कोशिश की, नहीं मिला यह काम करने के लिए)
  • या डेटा भंडारण परत

मैं पिछले एक का उपयोग करना चाहते में बाधा जोड़ें। लेकिन फिर मैं सोच रहा हूं कि कैसे मैं इसे लागू करूंगा। लेकिन सबसे पहले मैं valid? का उपयोग करें और फिर मैं का उपयोग

def persist! 
    @account = Account.create!(name: account_name) 
    @user = @account.users.create!(name: name, email: email) 
    rescue ActiveRecord::RecordNotUnique 
    errors.add(:name, "not unique") 
    false 
    end 

अब मैं दो चेकों मेरी कक्षा में चल रहा है,:

मैं की तरह कुछ कर सकते हैं (metaprogramming, मैं कुछ अन्य क्षेत्रों को संशोधित करने की आवश्यकता होगी) डेटा स्टोरेज बाधाओं के लिए rescue कथन।

क्या कोई इस मुद्दे को संभालने के लिए एक अच्छा तरीका जानता है? इसके लिए शायद अपने स्वयं के सत्यापनकर्ता को लिखना बेहतर होगा (लेकिन फिर मेरे पास डेटाबेस में दो प्रश्न होंगे, जहां आदर्श रूप से पर्याप्त होगा)।

+0

इस किसी को भी मदद कर सकते हैं: एक ऐसी ही स्थिति में मैं "ActiveModel :: सत्यापन" के बदले "ActiveRecord :: सत्यापन" में शामिल हैं - इस तरह से * validates_uniqueness_of * है में उपलब्ध – Mat

उत्तर

8

ब्रायन comment on my question to his blog post के लिए बहुत दयालु था।

class UniquenessValidator < ActiveRecord::Validations::UniquenessValidator 
    def setup(klass) 
    super 
    @klass = options[:model] if options[:model] 
    end 

    def validate_each(record, attribute, value) 
    # UniquenessValidator can't be used outside of ActiveRecord instances, here 
    # we return the exact same error, unless the 'model' option is given. 
    # 
    if ! options[:model] && ! record.class.ancestors.include?(ActiveRecord::Base) 
     raise ArgumentError, "Unknown validator: 'UniquenessValidator'" 

    # If we're inside an ActiveRecord class, and `model` isn't set, use the 
    # default behaviour of the validator. 
    # 
    elsif ! options[:model] 
     super 

    # Custom validator options. The validator can be called in any class, as 
    # long as it includes `ActiveModel::Validations`. You can tell the validator 
    # which ActiveRecord based class to check against, using the `model` 
    # option. Also, if you are using a different attribute name, you can set the 
    # correct one for the ActiveRecord class using the `attribute` option. 
    # 
    else 
     record_org, attribute_org = record, attribute 

     attribute = options[:attribute].to_sym if options[:attribute] 
     record = options[:model].new(attribute => value) 

     super 

     if record.errors.any? 
     record_org.errors.add(attribute_org, :taken, 
      options.except(:case_sensitive, :scope).merge(value: value)) 
     end 
    end 
    end 
end 

तुम इतनी तरह अपने ActiveModel कक्षाओं में उपयोग कर सकते हैं: उसकी मदद के साथ, मैं निम्नलिखित कस्टम सत्यापनकर्ता ले कर आए हैं

validates :account_name, 
    uniqueness: { case_sensitive: false, model: Account, attribute: 'name' } 

समस्या सिर्फ आप इस के साथ होगा, यदि आपका कस्टम model वर्ग में सत्यापन भी है। जब आप Signup.new.save पर कॉल करते हैं, तो वे सत्यापन तब नहीं चलते हैं, इसलिए आपको उन्हें किसी अन्य तरीके से देखना होगा। आप persist! विधि के अंदर हमेशा save(validate: false) का उपयोग कर सकते हैं, लेकिन फिर आपको यह सुनिश्चित करना होगा कि सभी सत्यापन Signup कक्षा में हैं, और उस श्रेणी को अद्यतित रखें, जब आप Account या User में कोई भी सत्यापन बदलते हैं।

+4

ध्यान दें कि रेल 4.1 में, '# setup' को वैधकर्ताओं पर बहिष्कृत किया गया है, और 4.2 में हटा दिया जाएगा। विधि को 'प्रारंभ करने' में बदलने के रूप में कार्य करना चाहिए। –

7

एक कस्टम सत्यापनकर्ता बनाना अधिक हो सकता है अगर यह एक-एक आवश्यकता हो।

एक सरलीकृत दृष्टिकोण ...

class Signup 

    (...) 

    validates :email, presence: true 
    validates :account_name, length: {within: 3..40}, format: { with: /^([a-z0-9\-]+)$/i } 

    # Call a private method to verify uniqueness 

    validate :account_name_is_unique 


    def persisted? 
    false 
    end 

    def save 
    if valid? 
     persist! 
     true 
    else 
     false 
    end 
    end 

private 

    # Refactor as needed 

    def account_name_is_unique 
    unless Account.where(name: account_name).count == 0 
     errors.add(:account_name, 'Account name is taken') 
    end 
    end 

    def persist! 
    @account = Account.create!(name: account_name) 
    @user = @account.users.create!(name: name, email: email) 
    end 
end 
+0

यह केवल नई वस्तुओं के लिए काम करेगा। रिकॉर्ड अपडेट करते समय आपको मौजूदा ऑब्जेक्ट के कारण डेटाबेस में पहले से ही एक त्रुटि प्राप्त होगी। – Hendrik

+1

यह एक साइनअप फॉर्म है, एक ऐसी क्रिया जो किसी दिए गए उपयोगकर्ता के जीवन चक्र में केवल एक बार होती है। :) लेकिन आपका बिंदु समझा जाता है। यदि आप इस फॉर्म ऑब्जेक्ट का पुन: उपयोग करना चाहते हैं, तो प्रत्येक मामले को संभालने के लिए एक दृष्टिकोण '# find_or_initialize_by' के बाद' #persisted 'हो सकता है। एक आसान वैकल्पिक दृष्टिकोण निरंतर ऑब्जेक्ट के संपादन और अपडेट के लिए एक अलग फॉर्म ऑब्जेक्ट होगा। – crftr

संबंधित मुद्दे