कई विदेशी कुंजियों के साथ मेल खाता है


84

मैं एक रिश्ते को परिभाषित करने के लिए एक मेज पर दो कॉलम का उपयोग करने में सक्षम होना चाहता हूं। इसलिए एक उदाहरण के रूप में एक कार्य ऐप का उपयोग करना।

प्रयास 1:

class User < ActiveRecord::Base
  has_many :tasks
end

class Task < ActiveRecord::Base
  belongs_to :owner, class_name: "User", foreign_key: "owner_id"
  belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end

तो फिर Task.create(owner_id:1, assignee_id: 2)

यह मुझे प्रदर्शन करने की अनुमति देता है Task.first.ownerजो उपयोगकर्ता को एकTask.first.assignee रिटर्न देता है और जो उपयोगकर्ता को दोUser.first.task रिटर्न देता है लेकिन कुछ भी नहीं लौटाता है। ऐसा इसलिए है क्योंकि कार्य उपयोगकर्ता से संबंधित नहीं है, वे स्वामी और असाइनमेंट से संबंधित हैं । इसलिए,

प्रयास 2:

class User < ActiveRecord::Base
  has_many :tasks, foreign_key: [:owner_id, :assignee_id]
end

class Task < ActiveRecord::Base
  belongs_to :user
end

यह सिर्फ दो विदेशी कुंजी के रूप में समर्थित होने के लिए नहीं लगता है कि पूरी तरह से विफल रहता है।

इसलिए मैं जो चाहता हूं, वह User.tasksदोनों उपयोगकर्ताओं के स्वामित्व और असाइन किए गए कार्यों को कहने और प्राप्त करने में सक्षम होना चाहिए ।

मूल रूप से किसी भी तरह एक रिश्ता है कि एक क्वेरी के बराबर होगा Task.where(owner_id || assignee_id == 1)

क्या यह संभव है?

अपडेट करें

मैं उपयोग नहीं finder_sqlकरना चाह रहा हूं , लेकिन इस मुद्दे का अस्वीकार्य जवाब मुझे जो चाहिए, उसके करीब लग रहा है: रेल - एकाधिक सूचकांक कुंजी प्रबंधन

तो यह तरीका इस तरह दिखेगा,

प्रयास 3:

class Task < ActiveRecord::Base
  def self.by_person(person)
    where("assignee_id => :person_id OR owner_id => :person_id", :person_id => person.id
  end 
end

class Person < ActiveRecord::Base

  def tasks
    Task.by_person(self)
  end 
end

हालाँकि मुझे इसमें काम करने के लिए मिल सकता है Rails 4, फिर भी मुझे निम्न त्रुटि मिलती रहती है :

ActiveRecord::PreparedStatementInvalid: missing value for :owner_id in :donor_id => :person_id OR assignee_id => :person_id

2
यह मणि क्या आप के लिए देख रहे हैं? github.com/composite-primary-keys/composite_primary_keys
mus

जानकारी के लिए धन्यवाद, लेकिन यह वह नहीं है जिसकी मुझे तलाश है। मैं किसी दिए गए मान के लिए या तो कॉलम चाहता हूं या कॉलम चाहता हूं। समग्र प्राथमिक कुंजी नहीं।
JonathanSimmons

हाँ, अद्यतन स्पष्ट करता है। मणि के बारे में भूल जाओ। हम दोनों ने सोचा कि आप केवल एक प्राथमिक प्राथमिक कुंजी का उपयोग करना चाहते हैं। कस्टम स्कोप वाले रिलेशनशिप को परिभाषित करके कम से कम यह संभव होना चाहिए। दिलचस्प परिदृश्य। मैं इसे बाद में
देखूंगा

FWIW यहां मेरा लक्ष्य किसी दिए गए उपयोगकर्ताओं को काम दिलाना है और ActiveRecord :: Relation format को बनाए रखना है ताकि मैं खोज / फ़िल्टरिंग के परिणाम पर कार्य क्षेत्र का उपयोग करना जारी रख सकूं।
जोनाथनसिमोंस

जवाबों:


80

टी एल; डॉ

class User < ActiveRecord::Base
  def tasks
    Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
  end
end

कक्षा has_many :tasksमें निकालें User


उपयोग करने का has_many :tasksकोई मतलब नहीं है क्योंकि हमारे पास user_idतालिका में नामित कोई कॉलम नहीं है tasks

मैंने अपने मामले में समस्या को हल करने के लिए क्या किया है:

class User < ActiveRecord::Base
  has_many :owned_tasks,    class_name: "Task", foreign_key: "owner_id"
  has_many :assigned_tasks, class_name: "Task", foreign_key: "assignee_id"
end

class Task < ActiveRecord::Base
  belongs_to :owner,    class_name: "User", foreign_key: "owner_id"
  belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
  # Mentioning `foreign_keys` is not necessary in this class, since
  # we've already mentioned `belongs_to :owner`, and Rails will anticipate
  # foreign_keys automatically. Thanks to @jeffdill2 for mentioning this thing 
  # in the comment.
end

इस तरह, आप कॉल कर सकते हैं User.first.assigned_tasksऔर साथ ही User.first.owned_tasks

अब, आप एक विधि को परिभाषित कर सकते हैं जिसे कहा जाता tasksहै assigned_tasksऔर का संयोजन लौटाता है owned_tasks

यह एक अच्छा समाधान हो सकता है जहाँ तक पठनीयता जाती है, लेकिन प्रदर्शन के दृष्टिकोण से, यह अब तक उतना अच्छा नहीं होगा, जिसे प्राप्त करने के लिए tasks, दो प्रश्नों को एक बार के बजाय जारी किया जाएगा, और फिर, परिणाम उन दो प्रश्नों के रूप में अच्छी तरह से शामिल होने की जरूरत है।

तो उपयोगकर्ता से संबंधित कार्यों को प्राप्त करने के लिए, हम निम्न तरीके से कक्षा में एक कस्टम tasksविधि को परिभाषित करेंगे User:

def tasks
  Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end

इस तरह, यह सभी परिणामों को एक ही क्वेरी में लाएगा, और हमें किसी भी परिणाम को मर्ज या संयोजित नहीं करना होगा।


यह समाधान महान है! यह क्रमशः असाइन, स्वामित्व और सभी कार्यों के लिए अलग-अलग पहुंच के लिए एक आवश्यकता है। एक बड़ी परियोजना पर कुछ ऐसा होगा जो बहुत महत्वपूर्ण होगा। हालांकि मेरे मामले में यह कोई आवश्यकता नहीं थी। के रूप में has_many"अर्थ निकालने नहीं" आप स्वीकार किए जाते हैं जवाब आप देखना चाहते हैं कि हम एक का उपयोग कर खत्म नहीं हुई पढ़ें has_manyसब पर घोषणा। अपनी आवश्यकताओं को मानते हुए हर दूसरे आगंतुक की आवश्यकताओं के साथ मेल खाना अनुत्पादक है, यह उत्तर कम निर्णय हो सकता है।
JonathanSimmons

यह कहा जा रहा है कि मेरा मानना ​​है कि यह बेहतर दीर्घकालिक सेटअप है जिसे हमें इस समस्या के साथ लोगों की वकालत करनी चाहिए। यदि आप अपने जवाब को संशोधित करेंगे तो अन्य मॉडलों के एक मूल उदाहरण और उपयोगकर्ता विधि को सम्मिलित करने के लिए कार्यों के संयुक्त सेट को पुनः प्राप्त करेंगे, मैं इसे चयनित उत्तर के रूप में संशोधित करूंगा। धन्यवाद!
JonathanSimmons

हाँ, मैं आपसे सहमत हूँ। का उपयोग करना owned_tasksऔर assigned_tasksसभी कार्यों को प्राप्त करने के लिए एक प्रदर्शन संकेत होगा। वैसे भी, मैंने अपना उत्तर अपडेट कर दिया है, और Userसभी संबद्ध प्राप्त करने के लिए कक्षा में एक विधि शामिल की है tasks, यह एक क्वेरी में परिणाम लाएगा, और कोई विलय / संयोजन करने की आवश्यकता नहीं है।
अर्सलान अली

2
सिर्फ एक FYI करें, Taskमॉडल में निर्दिष्ट विदेशी कुंजी वास्तव में आवश्यक नहीं है। चूँकि आपके belongs_toनाम रिश्ते हैं :ownerऔर :assignee, रेलें डिफ़ॉल्ट रूप से मान लेंगी कि क्रमशः विदेशी कुंजी नाम owner_idऔर assignee_idहैं।
jeffdill2

1
@ अर्सलानअली कोई समस्या नहीं। :-)
jeffdill2

47

ऊपर दिए गए @ dre-hh के उत्तर पर विस्तार करते हुए, जो मैंने पाया कि अब रेल 5 में अपेक्षित काम नहीं करता है। यह प्रतीत होता है कि रेल 5 में अब एक डिफ़ॉल्ट शामिल है WHERE tasks.user_id = ?जिसके प्रभाव के लिए क्लॉज़ है , जो विफल रहता है क्योंकि user_idइस परिदृश्य में कोई स्तंभ नहीं है।

मैंने पाया है कि यह अभी भी एक has_manyसंघ के साथ काम कर पाने के लिए संभव है , आपको बस इस अतिरिक्त को रोकना होगा जहां रेल द्वारा जोड़ा गया खंड है।

class User < ApplicationRecord
  has_many :tasks, ->(user) {
    unscope(:where).where(owner: user).or(where(assignee: user)
  }
end

धन्यवाद @Dwight, मैंने ऐसा कभी नहीं सोचा होगा!
गाबे कोपले

यह काम करने के लिए नहीं मिल सकता है belongs_to(जहां माता-पिता के पास कोई आईडी नहीं है, इसलिए इसे बहु-स्तंभ पीके पर आधारित होना चाहिए)। यह सिर्फ यह कहता है कि संबंध शून्य है (और कंसोल को देखते हुए, मैं लैंबडा क्वेरी को कभी निष्पादित नहीं कर सकता)।
fgblomqvist

@fgblomqvist अंतर्गत_तो के पास एक विकल्प होता है, जिसका नाम है: Primary_key, वह विधि निर्दिष्ट करें जो संबद्धता के लिए प्रयुक्त संबद्ध वस्तु की प्राथमिक कुंजी लौटाती है। डिफ़ॉल्ट रूप से यह आईडी है। मुझे लगता है कि यह आपकी मदद कर सकता है।
अहमद कमाल

@AhmedKamal मैं यह नहीं देखता कि यह कैसे उपयोगी है?
fgblomqvist 16

24

रेल 5:

यदि आपको अभी भी has_many associaiton चाहिए, तो आपको डिफ़ॉल्ट पर रोकना होगा जहां खंड @Dwight उत्तर देखें।

हालांकि User.joins(:tasks)मुझे देता है

ArgumentError: The association scope 'tasks' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.

जैसा कि यह अब संभव नहीं है आप @ अर्सलान अली समाधान का उपयोग कर सकते हैं।

रेल 4:

class User < ActiveRecord::Base
  has_many :tasks, ->(user){ where("tasks.owner_id = :user_id OR tasks.assignee_id = :user_id", user_id: user.id) }
end

Update1: @JonathanSimmons टिप्पणी के बारे में

उपयोगकर्ता मॉडल के दायरे में उपयोगकर्ता ऑब्जेक्ट को पास करने के लिए एक बैकवर्ड दृष्टिकोण की तरह लगता है

आपको उपयोगकर्ता मॉडल को इस दायरे में पास करने की आवश्यकता नहीं है। वर्तमान उपयोगकर्ता का उदाहरण इस लंबोदर के लिए स्वचालित रूप से पारित हो जाता है। इसे इस तरह से कॉल करें:

user = User.find(9001)
user.tasks

Update2:

यदि संभव हो तो आप इस उत्तर को विस्तार से बता सकते हैं कि क्या हो रहा है? मैं इसे बेहतर तरीके से समझना चाहता हूं ताकि मैं कुछ इसी तरह से लागू कर सकूं। धन्यवाद

has_many :tasksActiveRecord क्लास पर कॉल करने से कुछ क्लास वेरिएबल में एक लैम्ब्डा फंक्शन स्टोर हो जाएगा और tasksअपनी ऑब्जेक्ट पर एक मेथड जेनरेट करने का एक फैंटेसी तरीका है , जिसे इस लैम्ब्डा कहेंगे। उत्पन्न विधि निम्नलिखित सूडोकोड के समान दिखाई देगी:

class User

  def tasks
   #define join query
   query = self.class.joins('tasks ON ...')
   #execute tasks_lambda on the query instance and pass self to the lambda
   query.instance_exec(self, self.class.tasks_lambda)
  end

end

1
इतना भयानक, हर जगह मैंने देखा कि लोग समग्र कुंजियों के लिए इस पुरानी मणि का उपयोग करने का सुझाव देने की कोशिश करते रहे
फायरड्रैगन

2
यदि संभव हो तो आप इस उत्तर को विस्तार से बता सकते हैं कि क्या हो रहा है? मैं इसे बेहतर तरीके से समझना चाहता हूं ताकि मैं कुछ इसी तरह से लागू कर सकूं। धन्यवाद
स्टीफन लीड

1
हालाँकि यह एसोसिएशन को बनाए रखता है, यह अन्य समस्याओं का कारण बनता है। डॉक्स से: नोट: इन संघों में शामिल होना, उत्सुक लोडिंग और प्रीलोडिंग पूरी तरह से संभव नहीं है। उदाहरण के निर्माण से पहले ये ऑपरेशन होते हैं और गुंजाइश को शून्य तर्क के साथ कहा जाएगा। यह अप्रत्याशित व्यवहार को जन्म दे सकता है और पदावनत कर दिया जाता है। api.rubyonrails.org/classes/ActiveRecord/Associations/…
s2t2

1
यह आशाजनक लग रहा है लेकिन रेल 5 के साथ अभी भी whereऊपर के
लंबोदर

1
हाँ, ऐसा प्रतीत होता है जैसे यह अब रेल में काम नहीं करता है 5.
ड्वाइट

13

मैंने इसके लिए एक समाधान निकाला। मैं किसी भी संकेत के लिए खुला हूं कि मैं इसे बेहतर कैसे बना सकता हूं।

class User < ActiveRecord::Base

  def tasks
    Task.by_person(self.id)
  end 
end

class Task < ActiveRecord::Base

  scope :completed, -> { where(completed: true) }   

  belongs_to :owner, class_name: "User", foreign_key: "owner_id"
  belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"

  def self.by_person(user_id)
    where("owner_id = :person_id OR assignee_id = :person_id", person_id: user_id)
  end 
end

यह मूल रूप से has_many एसोसिएशन को ओवरराइड करता है लेकिन फिर भी उस ActiveRecord::Relationवस्तु को वापस करता है जिसकी मुझे तलाश थी।

तो अब मैं कुछ ऐसा कर सकता हूं:

User.first.tasks.completed और परिणाम सभी पूर्ण कार्य स्वामित्व या पहले उपयोगकर्ता को सौंपा गया है।


क्या आप अभी भी अपने प्रश्न को हल करने के लिए इस पद्धति का उपयोग कर रहे हैं? मैं उसी नाव में हूं और सोच रहा हूं कि क्या आपने कोई नया तरीका सीखा है या यदि यह अभी भी सबसे अच्छा विकल्प है।
डैन

अभी भी सबसे अच्छा विकल्प मुझे मिल गया है।
JonathanSimmons

यह संभवतः पुराने प्रयासों का अवशेष है। इसे हटाने का उत्तर संपादित किया।
JonathanSimmons

1
क्या यह और dre-hh का जवाब नीचे एक ही बात को पूरा करेगा?
डेक्निफिन

1
> उपयोगकर्ता मॉडल के दायरे में उपयोगकर्ता ऑब्जेक्ट को पास करने के लिए एक बैकवर्ड दृष्टिकोण की तरह लगता है। आपको कुछ भी पास करने की आवश्यकता नहीं है, यह एक लंबोदर है और वर्तमान उपयोगकर्ता उदाहरण इसके लिए स्वचालित रूप से पारित हो जाता है
dre-hh

2

संघों के लिए मेरा जवाब और (बहु) विदेशी कुंजी रेल में (3.2): उन्हें मॉडल में कैसे वर्णन किया जाए, और माइग्रेशन लिखना सिर्फ आपके लिए है!

आपके कोड के लिए, यहाँ मेरे संशोधन हैं

class User < ActiveRecord::Base
  has_many :tasks, ->(user) { unscope(where: :user_id).where("owner_id = ? OR assignee_id = ?", user.id, user.id) }, class_name: 'Task'
end

class Task < ActiveRecord::Base
  belongs_to :owner, class_name: "User", foreign_key: "owner_id"
  belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end

चेतावनी: यदि आप RailsAdmin का उपयोग कर रहे हैं और नया रिकॉर्ड बनाने या मौजूदा रिकॉर्ड को संपादित करने की आवश्यकता है, तो कृपया ऐसा न करें जो मैंने सुझाया है। क्योंकि इस हैक के कारण आपको इस तरह की समस्या का सामना करना पड़ेगा:

current_user.tasks.build(params)

कारण यह है कि रेल task.user_id को भरने के लिए current_user.id का उपयोग करने की कोशिश करेगी, केवल यह खोजने के लिए कि user_id जैसा कुछ नहीं है।

इसलिए, मेरी हैक विधि को बॉक्स के बाहर एक तरीके के रूप में समझें, लेकिन ऐसा न करें।


सुनिश्चित नहीं है कि यह उत्तर कुछ भी प्रदान करता है स्वीकृत उत्तर पहले से ही नहीं था। इसके अलावा, यह एक और प्रश्न के लिए एक विज्ञापन की तरह लगता है।
JonathanSimmons

@JonathanSimmons धन्यवाद। यदि कोई विज्ञापन की तरह खराब होता है तो मैं इस उत्तर को हटा दूंगा।
सूर्यास्त

2

5 रेल के बाद से आप वह भी कर सकते हैं जो ActiveRecord सुरक्षित तरीका है:

def tasks
  Task.where(owner: self).or(Task.where(assignee: self))
end
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.