Dodawanie rekordu z małymi modyfikacjami w kontrolerze

Witam,
zaznaczam że w RoRze (i ogólnie w Rubim) kodzę dopiero pierwszy dzień i trochę się gubię.

Idea jest taka - serwis, który przechowuje nagrody, z możliwością ich wylosowania (autoryzacja przez http). Mam oddzielne nagrody dla dwóch “obozów”, czyli jak zwycięzca jest z 1 obozu, losuję nagrodę z 1, jak z 2 to losuję z 2 : )

Tabela winners prezentuje się tak:
nick:string -> nick tego, który bierze udział w loterii
fraction:string -> oboz tego, ktory bierze udzial w loterii
reward:references -> jaką nagrodę wylosował
Rewards:
name:string -> nazwa
ally:integer -> ilosc tej nagrody w 1 obozie
horda:integer -> ilosc tej nagrody w 2 obozie
rate:integer -> wartosc nagrody, im wyzsza ta liczba, tym latwiej trafic ta nagrode

Kontroler winners:

[code=ruby] def new
@winner = Winner.new

respond_to do |format|
  format.html # new.html.erb
  format.xml  { render :xml => @reward }
end

end

POST /winners

POST /winners.xml

def create
@winner = Winner.new(params[:winner])

respond_to do |format|
  if @winner.save
    available_rewards = Reward.where(["? > ?", @winner.fraction, 0])
    array = []
    available_rewards.each do |reward|
        howmany = reward.rate * reward.ally if @winner.fraction == 'ally'
        howmany = reward.rate * reward.horda if @winner.fraction == 'horda'
        howmany.times do
            array.push(reward.id)
        end
    end
    array.shuffle
    reward = Reward.find(array[rand(array.size)])
    reward.ally = reward.ally - 1 if @winner.fraction == 'ally'
    reward.horda = reward.horda - 1 if @winner.fraction == 'horda'
    reward.save
    @winner.reward = reward.id
    @winner.save
    format.html { redirect_to('/winners', :notice => @winner.nick+' wygrywa '+reward.name+'.') }
    format.xml  { render :xml => @winner, :status => :created, :location => @winner }
  else
    format.html { render :action => "new" }
    format.xml  { render :xml => @winner.errors, :status => :unprocessable_entity }
  end
end

end[/code]
Formularz losowania:

[code=ruby]

Losuj nagrodę

<%= form_for(@winner) do |f| %>
<% if @winner.errors.any? %>

<%= pluralize(@winner.errors.count, "error") %> błędów:

    <% @winner.errors.full_messages.each do |msg| %>
  • <%= msg %>
  • <% end %>
<% end %>
<%= f.label :nick %>
<%= f.text_field :nick %>
<%= f.label :fraction %>
<%= f.text_field :fraction %>
<%= f.submit %>
<% end %>

<%= link_to ‘Wróć’, rewards_path %>[/code]
Efekt jest taki, że po wypełnieniu formularza, przechodzi do /winners (listy zwyciezcow) i… właściwie tyle. Nie ma zwycięzcy, ilość nagród nie została odjęta… Co tu jest źle, bo nawet logi serwera specjalnie nic mi nie mówią, ale wydaje mi się (zresztą, to chyba oczywiste), że formularz nie przechodzi do “create”. Pozdrawiam

Świstak

EDIT: Tak, może dodam jak chciałem rozwiązać (i jak wcześniej rozwiązałem w PHP) losowanie nagrody.
Dodaję tyle razy do tablicy daną nagrodę, wynik mnożenia rate, czyli wartości (im większa, tym łatwiej trafić) i ilości danej nagrody “na magazynie” w danej frakcji. Czyli np. dla rate = 5 i ilości 7 dla ally, wrzucam 35 razy do tablicy ID tej nagrody. Potem wszystko mieszam, mieszam, losuję jeden z tablicy - i powinienem mieć nagrodę. Potem formalności, czyli dodanie zwycięzcy do bazy, odjęcie 1 sztuki danej nagrody w danym obozie i tyle : )

2 porady:
1.

logger.info "Some debugging info I want to see in my development log." #W dowolnej linijce, można oczywiście wrzucać zmienne, efekt będziesz miał w konsoli serwera
  1. Taki kod w kontrolerze jest niedopuszczalny. Przenieś go do after_save modelu Winner

Zgaduję: masz bardzo dużo kodu między respond_to a format.html. Tak jak powiedział Tubis - o wiele za dużo i nie powinien tam być, ale na początek żeby zadziałało spróbuj przenieść go przed respond_to, albo do środka format.html, czyli:

respond_to do |format| format.html do #masa kodu end format.xml end

Zarówno wsadzenie kodu bezpośrednio do respond_to; do format.html; do after_save w modelu nie daje efektu.
Log z serwera w czasie wykonywania losowania:

[code]Started GET “/losuj” for 127.0.0.1 at 2010-07-13 14:11:47
Processing by WinnersController#new as HTML

Rendered winners/new.html.erb within layouts/application (14.8ms)
Completed 200 OK in 40ms (Views: 19.1ms | ActiveRecord: 0.0ms)

Started POST “/winners” for 127.0.0.1 at 2010-07-13 14:11:51
Processing by WinnersController#index as HTML
Parameters: {“authenticity_token”=>“phg8eDvqepAHVm2Ff9esOpj+Al2/kt8KYWP0HLVufSw=”, “winner”=>{“nick”=>“Maciek”, “fraction”=>“horda”}, “commit”=>“Create Winner”}
Winner Load (0.4ms) SELECT “winners”.* FROM “winners”
Rendered winners/_list.html.erb (0.7ms)

Rendered winners/index.html.erb within layouts/application (5.4ms)
Completed 200 OK in 27ms (Views: 9.1ms | ActiveRecord: 0.4ms)[/code]
Przepraszam, ale nie nadmieniłem, że korzystam z Rails 3b, ruby 1.9.2-head (przez rvm).

EDIT: Dalej mam wrażenie, że kod nie przechodzi z new -> create -> /winners tylko new -> /winners. Skąd Ruby wie, że z metody new powinien przejść do create? Rewards stworzyłem scaffoldem i nie znalazłem tam nic typu “action” (), więc uznałem, że Ruby o tym ‘wie’. Może powinienem coś gdzieś dopisać?

Rails korzysta z REST (dokładniejszy opis: http://guides.rubyonrails.org/routing.html), stąd “wie”, że powinno wykonać akcję create. Konkretnie załatwia ci to linia

<%= form_for(@winner) do |f| %>

Czy w pliku config/routes.rb masz coś w rodzaju

map.resources :winners

?

[quote=squil]Rails korzysta z REST (dokładniejszy opis: http://guides.rubyonrails.org/routing.html), stąd “wie”, że powinno wykonać akcję create. Konkretnie załatwia ci to linia

<%= form_for(@winner) do |f| %>

Czy w pliku config/routes.rb masz coś w rodzaju

map.resources :winners

?[/quote]
Mam, mianowicie bez “map”, ale to nic nie zmienia, zresztą jest o tym komentarz w routes.

routes.rb:

root :to => "home#index" match 'rewards/ally' => 'rewards#ally' match 'rewards/horda' => 'rewards#horda' match 'winners' => 'winners#index' match 'losuj' => 'winners#new' resources :winners resources :rewards
Teraz mam jakiś inny błąd, ale dla mnie dużo lepszy błąd, niż bez błędu jeśli i tak nie działa : P
Po wypełnieniu formularza, pluje mi:

[code]SQLite3::SQLException: no such column: rewards.winner_id: SELECT “rewards”.* FROM “rewards” WHERE (“rewards”.winner_id = 1) LIMIT 1

app/controllers/winners_controller.rb:42:in block (2 levels) in create' app/controllers/winners_controller.rb:25:increate’[/code]
A tutaj jeszcze raz treść kontrolera, oznaczyłem linie 25 i 42.

[code=ruby]def create
@winner = Winner.new(params[:winner])

25: respond_to do |format|
if @winner.save
format.html do
available_rewards = Reward.where(["? > ?", @winner.fraction, 0])
array = []
available_rewards.each do |reward|
howmany = reward.rate * reward.ally if @winner.fraction == ‘ally’
howmany = reward.rate * reward.horda if @winner.fraction == ‘horda’
howmany.times do
array.push(reward.id)
end
end
array.shuffle
reward = Reward.find(array[rand(array.size)])
reward.ally = reward.ally - 1 if @winner.fraction == ‘ally’
reward.horda = reward.horda - 1 if @winner.fraction == ‘horda’
reward.save
42: @winner.reward = reward.id
@winner.save
redirect_to(’/winners’, :notice => @winner.nick+’ wygrywa ‘+reward.name+’.’)
end
format.xml { render :xml => @winner, :status => :created, :location => @winner }
else
format.html { render :action => “new” }
format.xml { render :xml => @winner.errors, :status => :unprocessable_entity }
end
end
end[/code]
Błąd mówi prawdę, nie mam w tabeli czegoś takiego jak rewards.winner_id, nie wiem skąd się to wzięło - ale jest za to winners.reward (line42)

A czy w modelach Reward i Winner masz coś w rodzaju

[code=ruby]# class Reward
belongs_to :winner

class Winner

has_many :rewards
#lub
has_one :reward[/code]
?

Tak, w klasie winner mam:

has_one :reward

w rewards nic, ale z tego co czytałem na guides.rails.info jest dobrze - zwycięzca ma jedną nagrodę, ale daną nagrodę może wygrać wiele osób.

EDIT: Właśnie sprawdziłem w sqlite - teraz, gdy wypluł błąd, rekord do bazy dodał się, jednak pole reward jest puste - program się wyrżnął przed drugim @winner.save.

Jest dobrze, ale Rails spodziewa się, że będziesz miał kolumnę winner_id w tabeli rewards. Dokładny opis relacji: http://guides.rubyonrails.org/association_basics.html.

Nie masz zrobionej odpowiedniej migracji:
http://www.apohllo.pl/guides/migrations.html

Zmieniłem już relację na odpowiednią - źle zrozumiałem relację has_one. : )

Dzięki wszystkim, szczególnie Tobie, squil - działa i sporo się nauczyłem ; )