2009-08-22 20 views
34

के माध्यम से मैं एक मानक है, जिनमें कई करने वाली कई उपयोगकर्ताओं और मेरे रेल अनुप्रयोग में भूमिकाओं के बीच के रिश्ते:रेल मुहावरा has_many में डुप्लिकेट से बचने के लिए:

class User < ActiveRecord::Base 
    has_many :user_roles 
    has_many :roles, :through => :user_roles 
end 

मुझे लगता है कि यदि कोई उपयोगकर्ता केवल सौंपा जा सकता है यह सुनिश्चित करना चाहते एक बार कोई भूमिका। डुप्लिकेट डालने का कोई भी प्रयास अनुरोध को अनदेखा करना चाहिए, त्रुटि फेंकना या सत्यापन विफलता का कारण नहीं होना चाहिए। जो मैं वास्तव में प्रस्तुत करना चाहता हूं वह एक "सेट" है, जहां सेट में पहले से मौजूद तत्व को सम्मिलित करना कोई प्रभाव नहीं पड़ता है। {1,2,3} यू {1} = {1,2,3}, {1,1,2,3} नहीं।

मुझे लगता है कि मैं इसे इस तरह कर सकते हैं:

user.roles << role unless user.roles.include?(role) 

या एक आवरण विधि (जैसे add_to_roles(role)) बनाने के द्वारा, लेकिन मैं कुछ मुहावरेदार जिस तरह से यह संघ के माध्यम से स्वत: बनाने के लिए के लिए उम्मीद की गई थी, ताकि मैं लिख सकता हूं:

user.roles << role # automatically checks roles.include? 

और यह मेरे लिए काम करता है। इस तरह, मुझे डुप्लिकेट की जांच करने या कस्टम विधि का उपयोग करने के लिए याद रखना नहीं है। क्या ढीले ढांचे में कुछ है जो मुझे याद आ रही है? मैंने पहले सोचा था कि: uniq विकल्प है_मैनी ऐसा करेगा, लेकिन यह मूल रूप से बस "विशिष्ट चुनें"।

क्या यह घोषणा करने का कोई तरीका है? यदि नहीं, तो शायद एक एसोसिएशन एक्सटेंशन का उपयोग कर?

 >> u = User.create 
     User Create (0.6ms) INSERT INTO "users" ("name") VALUES(NULL) 
    => #<User id: 3, name: nil> 
    >> u.roles << Role.first 
     Role Load (0.5ms) SELECT * FROM "roles" LIMIT 1 
     UserRole Create (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3) 
     Role Load (0.4ms) SELECT "roles".* FROM "roles" INNER JOIN "user_roles" ON "roles".id = "user_roles".role_id WHERE (("user_roles".user_id = 3)) 
    => [#<Role id: 1, name: "1">] 
    >> u.roles << Role.first 
     Role Load (0.4ms) SELECT * FROM "roles" LIMIT 1 
     UserRole Create (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3) 
    => [#<Role id: 1, name: "1">, #<Role id: 1, name: "1">]

उत्तर

23

जब तक संलग्न भूमिका एक ActiveRecord वस्तु, तुम क्या कर रहे है:

user.roles << role 

चाहिए :has_many संघों के लिए स्वचालित रूप से डी-डुप्लिकेट।

has_many :through के लिए, कोशिश:

class User 
    has_many :roles, :through => :user_roles do 
    def <<(new_item) 
     super(Array(new_item) - proxy_association.owner.roles) 
    end 
    end 
end 

अगर सुपर काम नहीं करता है, तो आप एक alias_method_chain स्थापित करने के लिए आवश्यकता हो सकती है।

validates_uniqueness_of :user_id, :scope => [:role_id] 
+0

यह ऐसा काम नहीं करता है। मैं परीक्षण को शामिल करने के लिए पोस्ट अपडेट करूंगा। – KingPong

+0

धन्यवाद, मैं एसोसिएशन एक्सटेंशन का प्रयास करूंगा। – KingPong

+0

यह पूरी तरह से काम किया। धन्यवाद! जिस हिस्से में मैं कुछ खोने की कोशिश करता था, वह हिस्सा मैं प्रॉक्सी_owner बिट था। – KingPong

0

शायद यह सत्यापन नियम

validates_uniqueness_of :user_roles 

बनाने के लिए तो सत्यापन अपवाद को पकड़ने और शान से पर ले जाने के लिए संभव है:

यहाँ कैसे डिफ़ॉल्ट व्यवहार में विफल रहता है, इसका एक उदाहरण। हालांकि, यह वास्तव में हैकी लगता है और यदि संभव हो तो बहुत ही सुरुचिपूर्ण है।

2

मुझे लगता है कि उचित सत्यापन नियम अपने users_roles में है मॉडल में शामिल होने के मॉडल में शामिल हों।

validates_uniqueness_of :user_id, :scope => [:role_id] 

class User 
    has_many :roles, :through => :user_roles do 
    def <<(*items) 
     super(items) rescue ActiveRecord::RecordInvalid 
    end 
    end 
end 
+0

धन्यवाद। यह वास्तव में ऐसा नहीं करता जो मैं चाहता हूं (जो एक सेट-जैसा व्यवहार है), और मैंने स्पष्ट किया है कि मूल पोस्ट में क्या है। उसके लिए क्षमा चाहता हूँ। – KingPong

+0

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

+1

हे, क्या तुम पागल हो? उपयोगकर्ता अपनी भूमिका नहीं जोड़ते हैं :-) सामान्य उपयोग केस यह है कि कोई उपयोगकर्ता किसी अन्य चीज़ के दुष्प्रभाव के रूप में भूमिका का सदस्य बन जाता है। उदाहरण के लिए, एक विशेष उत्पाद खरीदना। अन्य उत्पाद भी एक ही भूमिका प्रदान कर सकते हैं, इसलिए वहां नकल के लिए एक मौका है। मैं किसी भी स्थान पर डुप्लिकेशन जांच करना चाहता हूं, जो कि किसी भी यादृच्छिक स्थानों को उपयोगकर्ता की भूमिका सुनिश्चित करने के लिए आवश्यक है। इस अर्थ में, उपयोगकर्ता को एक भूमिका निभाने के लिए पहले से ही एक त्रुटि शर्त नहीं है। – KingPong

3

आप मुख्य मॉडल में validates_uniqueness_of और अधिभावी < < के संयोजन का उपयोग कर सकते हैं, हालांकि यह भी किसी अन्य मान्यता त्रुटियां पकड़ सकता है:

+1

क्या आप उस अपवाद को 'ActiveRecord :: RecordNotUnique' में नहीं बदल सका? मुझे यह जवाब पसंद है। [दौड़ की स्थितियों] से अवगत रहें (http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of) हालांकि। – Ashitaka

+0

अच्छा जवाब। मैंने इसे 'validates_uniqueness_of' के बिना इस्तेमाल किया, डेटाबेस में अद्वितीय इंडेक्स घोषित किया और आकर्षक काम करता है। –

0

मुझे लगता है कि आप की तरह कुछ करना चाहता हूँ:

user.roles.find_or_create_by(role_id: role.id) # saves association to database 
user.roles.find_or_initialize_by(role_id: role.id) # builds association to be saved later 
0

मैं आज में पड़ गए और #replace है, जो "एक diff प्रदर्शन कर दिए जाएंगे और/केवल रिकॉर्ड बदलने के बाद कि जोड़ें" का उपयोग कर समाप्त हो गया ।

इसलिए, आप (ताकि वे हटा दिया जाता है नहीं है) मौजूदा भूमिका के लिए संघ पास करनी होगी और अपनी नई भूमिका (ओं):

new_roles = [role] 
user.roles.replace(user.roles | new_roles) 

यह है कि दोनों इस जवाब ध्यान देना महत्वपूर्ण है और स्वीकार किया गया है कि एरे diff (-) और यूनियन (|) करने के लिए स्वीकृत roles ऑब्जेक्ट्स को स्मृति में लोड कर रहे हैं। यदि आप बड़ी संख्या में संबंधित रिकॉर्ड से निपट रहे हैं तो यह प्रदर्शन समस्याओं का कारण बन सकता है।

यदि यह कोई चिंता है, तो आप उन विकल्पों को देखना चाहते हैं जो पहले प्रश्नों के माध्यम से अस्तित्व की जांच करते हैं, या डालने के लिए INSERT ON DUPLICATE KEY UPDATE (mysql) प्रकार क्वेरी का उपयोग करते हैं।

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