Szyny z wieloma kluczami zagranicznymi
Chcę być w stanie użyć dwóch kolumn na jednej tabeli do zdefiniowania relacji. Więc za pomocą aplikacji Zadań jako przykład.
Próba 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
Więc Task.create(owner_id:1, assignee_id: 2)
To pozwala mi wykonać Task.first.owner
który zwraca user one i Task.first.assignee
który zwraca user two Ale User.first.task
nie zwraca nic. Ponieważ zadanie nie należy do użytkownika, należy do właściciela i cesjonariusza . Więc
Próba 2:
class User < ActiveRecord::Base
has_many :tasks, foreign_key: [:owner_id, :assignee_id]
end
class Task < ActiveRecord::Base
belongs_to :user
end
To po prostu całkowicie zawodzi, ponieważ dwa klucze obce nie wydają się być obsługiwane.
Chcę więc być w stanie powiedzieć User.tasks
i uzyskać zarówno własne, jak i przypisane zadania.
W zasadzie jakoś zbudować relację, która byłaby równa zapytaniu Task.where(owner_id || assignee_id == 1)
Update
Nie chcę używać finder_sql
, ale odpowiedź tego problemu wygląda na zbliżoną do tego, czego chcę: Rails - Multiple Index Key Stowarzyszenie
Więc ta metoda wyglądałaby tak,
Próba 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
Chociaż mogę go uruchomić w Rails 4
, ciągle dostaję następujący błąd:
ActiveRecord::PreparedStatementInvalid: missing value for :owner_id in :donor_id => :person_id OR assignee_id => :person_id
5 answers
TL;DR
class User < ActiveRecord::Base
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
end
Usuń has_many :tasks
w klasie User
.
Użycie has_many :tasks
w ogóle nie ma sensu, ponieważ nie mamy żadnej kolumny o nazwie user_id
w tabeli tasks
.
Co zrobiłem, aby rozwiązać problem w moim przypadku jest:
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
W ten sposób można wywołać User.first.assigned_tasks
jak również User.first.owned_tasks
.
Teraz możesz zdefiniować metodę o nazwie tasks
, która zwraca kombinację assigned_tasks
i owned_tasks
.
To może być dobre rozwiązanie jeśli chodzi o czytelność, ale z z punktu widzenia wydajności, nie byłoby to tak dobre jak teraz, aby uzyskać tasks
, dwa zapytania zostaną wydane zamiast raz, a następnie wynik tych dwóch zapytań należy również połączyć.
Więc aby uzyskać zadania, które należą do użytkownika, zdefiniujemy niestandardową metodę tasks
w klasie User
w następujący sposób:
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
W ten sposób pobierze wszystkie wyniki w jednym zapytaniu i nie będziemy musieli łączyć ani łączyć żadnych wyników.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-05 00:10:49
Rails 5:
Musisz rozszyfrować domyślną klauzulę where zobacz odpowiedź @Dwight, jeśli nadal chcesz mieć asocjację has_many.
Choć User.joins(:tasks)
daje mi
ArgumentError: The association scope 'tasks' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.
Ponieważ nie jest to już możliwe, Możesz również użyć @Arslan Ali solution.
Rails 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: Regarding @ JonathanSimmons comment
Przekazanie obiektu user do scope w modelu User wydaje się być podejściem wstecznym
Nie masz aby przekazać model użytkownika do tego zakresu. Bieżąca instancja użytkownika jest przekazywana automatycznie do tej lambda. Nazwij to tak:
user = User.find(9001)
user.tasks
Update2:
Jeśli to możliwe, czy mógłbyś rozwinąć tę odpowiedź, aby wyjaśnić, co się dzieje? Chciałbym to lepiej zrozumieć, aby móc wdrożyć coś podobnego. dzięki
Wywołanie has_many :tasks
w klasie ActiveRecord zapisze funkcję lambda w jakiejś zmiennej klasy i jest po prostu fantazyjnym sposobem na wygenerowanie metody tasks
na jej obiekcie, która zadzwonię do lambdy. Wygenerowana metoda wyglądałaby podobnie do następującego pseudokodu:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-16 11:38:28
Wymyśliłem na to rozwiązanie. Jestem otwarty na wszelkie wskazówki, jak Mogę to poprawić.
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
To w zasadzie nadpisuje asocjację has_many, ale nadal zwraca obiekt ActiveRecord::Relation
, którego szukałem.
Więc teraz mogę zrobić coś takiego:
User.first.tasks.completed
a wynikiem jest całe ukończone zadanie należące lub przypisane do pierwszego użytkownika.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-03 22:39:33
Rozszerzenie na odpowiedź @dre-hh powyżej, która już nie działa zgodnie z oczekiwaniami w Rails 5. Wygląda na to, że Rails 5 zawiera teraz domyślną klauzulę where na skutek WHERE tasks.user_id = ?
, która nie działa, ponieważ w tym scenariuszu nie ma kolumny user_id
.
I ' ve found it is still possible to get it working with a has_many
association, you just need to unscope this additional where clause added by Rails.
class User < ApplicationRecord
has_many :tasks, ->(user) { unscope(:where).where("owner_id = :id OR assignee_id = :id", id: user.id) }
end
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-07 02:14:59
Moja odpowiedź na skojarzenia i (wiele) kluczy obcych w rails (3.2): jak opisać je w modelu i zapisać migracje jest właśnie dla Ciebie!
Jeśli chodzi o Twój kod,oto moje modyfikacje
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
Ostrzeżenie: Jeśli używasz RailsAdmin i chcesz utworzyć nowy rekord lub edytować istniejący rekord, nie rób tego, co sugeruję.Bo ten hack sprawi problem jak zrobisz coś takiego:
current_user.tasks.build(params)
Powodem jest to, że rails będzie próbował użycie current_user.id aby wypełnić zadanie.user_id, tylko po to, aby stwierdzić, że nie ma czegoś takiego jak user_id.
Więc potraktuj moją metodę hakowania jako wyjście poza pole, ale nie rób tego.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:41