validates_uniqueness_of w użyciu

Chce uniemożliwić zapisanie wartości match_id jeżeli już taka sama jest w bazie, więc w modelu bet mam: validates_uniqueness_of :match_id.

kawałek tabeli w bazie:

t.integer "user_id" t.integer "match_id"
match_id jest zapisywane po wybieraniu z listy. problem polega na tym, że jeżeli w bazie jest już taka wartość jak ta która ma być dodana - to jest rozpoznawana, ale zamiast informacji to wyskakuje błąd:

[code=ruby]You have a nil object when you didn’t expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.each

Extracted source (around line #12):

9:


10: Mecz

11:
12: <% @match.each do |match| %>
13:

14: <%= match.team_1 %>
15: [/code]
coś muszę przekazać po tej walidacji, tylko nie wiem za bardzo co przekazać żeby lista nie była pusta ?

Jak chodzi o linię 12 to na pierwszy rzut oka powinno być @maches.each, zamiast @match

Zresztą nawet jak to zmienisz, to nie wiem czy to dalej pójdzie. A właściwie na pewno dalej nie pójdzie, bo select ma złe name/id i nie zostanie rozpoznany jak mach do modelu bet. Są helpery do robienia selecta, a jak robisz to ręcznie, to nadawaj właściwe id. No chyba ze w kontrolerze jedziesz nie standardowo, tylko własno ręcznie, to zwracam honor

jako, że niestety nie udaje mi się znaleźć rozwiązania, zamieszczam więcej informacji, a nóż kto coś podpowie:

[code=ruby]def new
@bet = Bet.new
@user = current_user.username
@match = Match.find(:all)
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @bet }
end
end

def create
@bet = Bet.new(params[:bet])
@bet.match_id = (params[:match])
@bet.user_id = current_user.id
respond_to do |format|
if @bet.save
flash[:notice] = ‘Twój typ został zapisany’
format.html { redirect_to(@bet) }
format.xml { render :xml => @bet, :status => :created, :location => @bet }
else
format.html { render :action => “new” }
format.xml { render :xml => @bet.errors, :status => :unprocessable_entity }
end
end
end[/code]

[code=ruby]<% form_for(@bet) do |f| %>
<%= f.error_messages %>

Mecz
<% @match.each do |match| %> <%= match.team_1 %> <% end %>

<% end %>[/code]
i to działa ale bez walidacji.
Po walidacji po validates_uniqueness_of :match_id jest jeszcze potrzebne match_id do wyświetlenia listy <% @match.each do |match| %> <option value="<%= match.id %>", ale wtedy nie jest już dostępne i wg moje rozumowania dlatego błąd się pojawia. Ale tak naprawde to wylko moje przypuszczenia na które i tak nie znajduje rozwiązania :frowning:

http://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/select
poczytaj sobie w wolnej chwili :slight_smile:
bo tak powinno robić się selecty

A tak na szybko w swoim kodzie spróbuj zmienić:

<% @match.each do |match| %>

na

<% Match.all.each do |match| %>

dzięki @fastred

teraz działa :smiley:

ten sposób co używałem to skądś był zapożyczony, jak widać nie wszystko co się znajdzie jest dobre.

<% Match.all.each do |match| %>

To powinno być w kontrolerze.
Dodaj do akcji create @match = Match.all
W akcji create obiekt nie może być zapisany(walidacja) i renderuje Ci się widok z new, ale w akcji create nie masz zainicjalizowanej zmiennej @match

[quote=staszek]<% Match.all.each do |match| %>
To powinno być w kontrolerze[/quote]
A ja z kolei uważam, że jedna zmienna na akcję w zupełności wystarczy :slight_smile:

Przeniesienie @match = Match.all do kontrolera:

  • duplikuje kod w new i create
  • nie ma wpływu na przepływ logiki w kontrolerze
  • jest związane z jednym tylko rodzajem widoku (HTML), jest zbędne przy wyświetlaniu JSON

Jak dla mnie oznaca to, że Match.all jest istotne tylko w jednym konkretnym widoku i tylko tam należy go użyć.

Ogólnie: uważam, że w widoku da się użyć każdej metody z modelu (nawet metody klasowej) jeśli spełnione są dwa warunki:

  • zachowane jest prawo Demeter
  • kod ten jest testowalny w unit teście (Match.all można z łatwością przetestować, nie twierdzę, że trzeba)

Wybiegając trochę w przyszłość: zależność fragmentu widoku od zmiennej ustawianej w kontrolerze może Ci uniemożliwić lub bardzo utrudnić ekstrakcję tej części do partiala oraz późniejsze jej cache’owanie.

O, dyskusja religijna :smiley:

Podejście podane przez Bragiego (wyciągnięcie danych w widoku) jest najbardziej pragmatyczne i chyba najprostsze w utrzymaniu (tzn. chyba zrobiłbym to tak samo, robię tak zawsze w partialach, które muszą być samowystarczalne/hermetyczne).

Natomiast jeśli się upierasz żeby być true kvlt MVC (podejście Bragiego zdradza jego słabość do architektury komponentowej :wink: ), możesz przypisanie do @match wyrzucić do metody kontrolera wywoływanej przez filtr ustawiony dla zarówno new jak i create.

no ok, ale jeżeli i to sytuacja jest nie teoretyczna, bo chce żeby:
zamiast:

@match = Match.all

zrobić:

@match = Match.find(:all, :conditions => coś tam ...

(o ile w ogóle tak można w tym konkretnym wypadku robić) tzn. wyświetlić na liście rozwijanej tylko określone rekordy z tabeli matches

to już zdaje się powinno być już raczej wtedy w kontrolerze ?

Niekoniecznie, zwłaszcza od kiedy ręcznie podawane :conditions są passe na rzecz named_scope’ów :wink:
Ale tu zostawię ewangelizację Bragiemu :smiley:

OK, to teraz poproszę wykład o architekturze komponentowej - bo przyznam, że nie mam pojęcia o czym mówisz. Przychylam się raczej Wikipediowej definicji, konkretnie punktom 3 i 4 z Overview:

[quote]3. The controller notifies the model of the user action, possibly resulting in a change in the model’s state. (For example, the controller updates the user’s shopping cart.)
4. A view queries the model in order to generate an appropriate user interface (for example, the view lists the shopping cart’s contents). Note that the view gets its own data from the model. The controller may (in some implementations) issue a general instruction to the view to render itself. In others, the view is automatically notified by the model of changes in state (Observer) which require a screen update.[/quote]

Sorry, pomerdało mi się – piłem oczywiście do resourców, które przeciwstawiłeś MVC tutaj: http://rubyonrails.pl/forum/p12480-2009-09-01-13:42:37#p12480

Chciałem zatrollować ale znowu fail :frowning:

<%= f.select(:match_id, Match.wybrane_mecze.collect {|m| [m.team_1, m.id]}, :prompt => 'Wybierz mecz') %>
named_scope :wybrane_mecze, :joins => :round, :conditions => [ "rounds.date > ?", Time.now ]

To co powyżej działa, ale mam pytanie jak powinien wyglądać select, żeby uniknąć named_scopa ?

[quote=tpl]<%= f.select(:match_id, Match.wybrane_mecze.collect {|m| [m.team_1, m.id]}, :prompt => 'Wybierz mecz') %>
To co powyżej działa, ale mam pytanie jak powinien wyglądać select, żeby uniknąć named_scopa ?[/quote]
Użyj helpera:

<%= f.select(:match_id, chosen_matches_for_select, :prompt => 'Wybierz mecz') %>

Kod chosen_matches_for_select chyba dasz radę napisać sam :slight_smile: