Rails idiom, aby uniknąć duplikatów w ma wiele: przez
Mam standardową relację między użytkownikami a rolami w mojej aplikacji Rails:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, :through => :user_roles
end
Chcę się upewnić, że użytkownik może być przypisany do każdej roli tylko raz. Każda próba Wstawienia duplikatu powinna ignorować żądanie, a nie wywoływać błędu lub powodować niepowodzenie walidacji. To, co naprawdę chcę reprezentować, to "zestaw", w którym wstawianie elementu, który już istnieje w zestawie, nie ma żadnego efektu. {1,2,3} U {1} = {1,2,3}, a nie {1,1,2,3}.
Zdaję sobie sprawę, że mogę to zrobić jak to:
user.roles << role unless user.roles.include?(role)
Lub tworząc metodę wrappera( np. add_to_roles(role)
), ale liczyłem na jakiś idiomatyczny sposób, aby uczynić ją automatyczną poprzez skojarzenie, żebym mógł napisać:
user.roles << role # automatically checks roles.include?
I to po prostu robi za mnie robotę. W ten sposób nie muszę pamiętać, aby sprawdzić dups lub użyć niestandardowej metody. Czy jest coś w ramce, o czym Nie wiem? Na początku myślałem, że opcja: uniq do has_many to zrobi, ale jest to po prostu " select distinct."
Czy jest na to sposób deklaratywnie? Jeśli nie, może używając rozszerzenia asocjacji?
Oto przykład, jak domyślne zachowanie się nie powiedzie:
>> 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">]
7 answers
Dopóki dołączona rola jest obiektem ActiveRecord, to co robisz:
user.roles << role
Powinien automatycznie usuwać duplikaty dla :has_many
asocjacji.
Dla has_many :through
, Spróbuj:
class User
has_many :roles, :through => :user_roles do
def <<(new_item)
super( Array(new_item) - proxy_association.owner.roles )
end
end
end
Jeśli super nie działa, może być konieczne skonfigurowanie alias_method_chain.
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
2012-12-17 05:08:39
Użyj tablicy |=
metoda Join.
Możesz użyć tablic |=
metoda join dodaje element do tablicy, chyba że jest już obecny. Tylko upewnij się, że zawijasz element w tablicę.
role #=> #<Role id: 1, name: "1">
user.roles #=> []
user.roles |= [role] #=> [#<Role id: 1, name: "1">]
user.roles |= [role] #=> [#<Role id: 1, name: "1">]
Może być również używany do dodawania wielu elementów, które mogą lub nie muszą być już obecne:
role1 #=> #<Role id: 1, name: "1">
role2 #=> #<Role id: 2, name: "2">
user.roles #=> [#<Role id: 1, name: "1">]
user.roles |= [role1, role2] #=> [#<Role id: 1, name: "1">, #<Role id: 2, name: "2">]
user.roles |= [role1, role2] #=> [#<Role id: 1, name: "1">, #<Role id: 2, name: "2">]
Znalazłem tę technikę na tej odpowiedzi StackOverflow .
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
2018-03-22 18:04:18
Możesz użyć kombinacji validates_uniqueness_of i overriding
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
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
2011-03-16 19:01:15
Myślę, że właściwa Reguła walidacji jest w modelu users_roles join:
validates_uniqueness_of :user_id, :scope => [:role_id]
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
2009-08-22 06:49:13
Być może istnieje możliwość utworzenia reguły walidacji
validates_uniqueness_of :user_roles
Następnie złap wyjątek walidacji i kontynuuj z wdziękiem. Jednak wydaje się to bardzo chwiejne i jest bardzo nieeleganckie, jeśli w ogóle jest to możliwe.
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
2009-08-22 05:41:55
Myślę, że chcesz zrobić coś takiego:
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
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-04-05 23:23:19
Wpadłem na to dzisiaj i skończyło się na #replace, które "wykona diff i usunie/doda tylko rekordy, które się zmieniły".
Dlatego musisz przekazać związek istniejących ról (aby nie zostały usunięte) i Twojej nowej roli(y):
new_roles = [role]
user.roles.replace(user.roles | new_roles)
Ważne jest, aby pamiętać, że zarówno ta odpowiedź, jak i zaakceptowana ładują powiązane obiekty roles
do pamięci w celu wykonania tablicy diff (-
) i union (|
). Może to prowadzić do wydajności problemy, jeśli masz do czynienia z dużą liczbą powiązanych rekordów.
Jeśli jest to problemem, możesz najpierw przyjrzeć się opcjom, które sprawdzają istnienie za pomocą zapytań lub użyć zapytania typu INSERT ON DUPLICATE KEY UPDATE
(mysql)do wstawiania.
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-04-25 18:16:27