CanCan - jak ustawić takie ability?

Nie bardzo wiem jak się zabrać za takie coś.
Mam modele User, Entry i EntryVote. User może głosować na Entry, wtedy dodawany jest do tabeli entry_votes rekord user_id, entry_id.
W widoku dla entries#show wyświetlam przyciski do głosowania w których wysyłam entry_id jako parametr. No i teraz chcę ustawić, że użytkownik:

can :create, EntryVote i cannot :create, EntryVote jeżeli jest już rekord z user_id i entry_id
Będę wdzięczny za jakieś podpowiedzi jak tego ‘cannot’ zrobić.

A dlaczego używać do tego CanCana, nie możesz po prostu założyć walidacji, żeby kombinacja pól user_id i entry_id była w modelu EntryVode unikalna?

coś jak

validates_uniqueness_of :entry_id, :scope => :user_id

Pomyślałem, że jakoś tak zgrabniej będzie jak w widoku dam tylko jednego ifa coś w stylu:

<% if can? :create, EntryVote.new(user_id: current_user.id, entry_id: @entry.id) %> <a href="#">+1</a> <% else %> <span>+1</span> <% end %>
Zamiast if can? i dodatkowo sprawdzenie czy nie ma takich rekordow.

Arnvald mówi o walidacji na poziomie modelu, nic nie musisz (ba, nawet nie powinieneś) robić w widoku. Walidacja następuje przed zapisem i w kontrolerze już obsługujesz sobie przypadek jakby się nie zapisało.

Wiem, ale chcę jakoś odróżnić w widoku czy pubnktowanie jest aktywne czy nie.

możesz w kontrolerze, na przykład utworzyć:
@entry_vote = EntryVote.new(user_id: current_user.id, entry_id: @entry.id)

a w widoku sprawdzać czy
@entry_vote.valid?

Tak pewnie zrobie, chodziło mi o to czy da się to wszystko jakoś zawrzeć w ability cancana.

ale po co to ma być w ability cancana? :slight_smile:

Teraz to już raczej kwestie estetyczne, jak podsuneliscie mi pomysl jak to zrobic inaczej. Więc teraz czysto teoretycznie :), gdybym takie coś chciał zrobić w ability cancana, to jak sie do tego zabrac?

CanCan działa tak ze sprawdza czy dany warunek ma true lub false, masz tam dostep do obiektu ktory przekazujesz, usera, i polaczonych rzeczy, mozesz tam dac blok i zwrocic ture false dla warnku i tyle

Nie znam składni CanCana ale generalnie mozesz zrobić +/- tak:

[code=ruby]class User

def voted?(entry)
EntryVote.where(user_id: self.id, entry_id: entry.id).present?
end[/code]

can :vote, Entry do |entry| !user.voted? entry end

<% if can? :vote, @entry %> <a href="#">+1</a> <% else %> <span>+1</span> <% end %>

Powiedzcie czy dobrze myślę. CanCan działa jak taki rozbudowany before_filter. Przez to, w modelu ability nie mam dostępu do zmiennych z kontrolera, bo te zmienne nie zostały jeszcze zainicjalizowane (chyba ze dodam load_resources). Powinienem mieć za to dostęp do zmiennej params.

Próbuję zrobić coś takiego (na podstawie tego co podrzucił sebcioz)
Przy linku na głosowanie uruchamiam EntryVotes#create z id entry w params[:id]

W ability cancana tworzę regułę:

cannot :create, EntryVote do |ev| user.voted? Entry.find(params[:id]) end
niestety coś tu nie działa, CanCan rzuca mi wyjątek AccessDenied dla Entry gdzie uzytkownik nie glosował, czyli user.voted? daje false.

Ok, udało mi się to zrobić :slight_smile: Może się komuś przyda.

Po pierwsze, żeby params były dostępne w modelu Ability trzeba w application_controller.rb dodać:

[code=ruby]private

def current_ability
@current_ability ||= Ability.new(current_user, params)
end[/code]
I odpowiednio zupdejtować wywołanie metody initialize modelu Ability:

def initialize(user, params)

W moim przypadku,w ustawieniu permissions dałem

unless !params[:id].nil? && user.voted?(Entry.find(params[:id])) can :create, EntryVote end
I wszystko śmiga tak jak chciałem.

I link: https://github.com/ryanb/cancan/wiki/Accessing-Request-Data