Tableless model, walidacja

Nie wiem czy ma w tym przypadku to do rzeczy, że jest to table less model (via http://railscasts.com/episodes/219-active-model),
ale możliwe, że nie dołączyłem jakiegoś modułu.

Controller:

[code] def new
@points_transfer = PointsTransfer.new
end

def create
  @points_transfer = PointsTransfer.new(params[:points_transfer])
  @points_transfer.sender = current_user
  
  if @points_transfer.save
    redirect_to panel_path, :notice => "Przelałeś użytkownikowi #{@points_transfer.receiver.username} #{@points_transfer.amount} punktów."
  else
    redirect_to panel_path, :notice => "Podczas wysyłania punktów wystąpił błąd."
  end
end[/code]

[code]class PointsTransfer
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming

attr_accessor :sender, :receiver
attr_reader :receiver, :amount

validate :amount_cannot_be_greater_than_sender_points
validates :amount, :presence => true

validates :receiver, :presence => true

def amount_cannot_be_greater_than_sender_points
errors.add(“Ilość punktów”, “nie może być wieksza niz ilosc posiadanych punktow”) if
@sender.points < @amount
end

def receiver=(val)
@receiver = User.find_by_username(val)
end

def amount=(val)
@amount = val.to_i
end

def save
if valid?
apply
end
end

def apply
@sender.points -= @amount
@receiver.points += @amount

@sender.save
@receiver.save

end

def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end

def persisted?
false
end
end[/code]
Czy da się zastąpić walidację amount_cannot_be_greater_than_sender_points przez coś w tym stylu:

validates :amount, :numericality => { :greater_than_or_equal_to => @sender.points }
Powyższy kod mi wypluwa błąd, że brak metody ‘points’ dla @sender, który jest nil.
Co właściwie jest racją, bo nie mam pojęcia, dlaczego walidacja jest odpalana w metodzie new.

Podejrzewam, że walidacja zachodzi dlatego, że w metodzie new tworzysz ‘pełnoprawny’ obiekt (który nie dziedziczy po ActiveRecord::Base czyli nie zapisujesz go w bazie, więc nie wywołasz na nim metody .save).

Ok, ale czemu w takim razie u Ryana w railcascie działa wszystko ok?
Czy to ma związek z tym, że utworzyłem metodę ‘save’? Wydawało mi się, że mogę spokojnie użyć tutaj tej nazwy, skoro nie dziedziczę po ActiveRecord::Base.

Jeszcze tylko dopiszę, czego nie napisałem, że są to Railsy 3.1.3 i Ruby 1.9.2.

Dziwne, na railsach 3.2 działa, tzn nie wywala wyjątku przy PointsTransfer.new i oczywiście nie wywołują się walidacje

Czyli to tylko kwestia wersji railsów?

validates :amount, :numericality => { :greater_than_or_equal_to => @sender.points }
metoda validates jest wykonywana w definicji klasy. klasa jest ładowana przez load missing constant, w momencie, jak pierwszy raz jej używasz (w new).

Metoda validates tylko definiuje walidację, nie wykonuje jej.

Zobacz jak zachowuje się ten kod

[code=ruby]class Cat
def self.acts_as_cat
puts “it’s a cat”
def voice
puts “mieow”
end
end

acts_as_cat
end

puts “Cat defined”
Cat.new.voice[/code]

jak chcesz wykonać ten kod w kontekście obiektu a nie klasy to musisz podać proca jako wartość dla klucza :greater_than_or_equal_to, czyli coś takiego:

validates :amount, :numericality => { :greater_than_or_equal_to => Proc.new{|this| this.sender.points} }
jak to działa możesz podglądnąć tutaj => https://github.com/rails/rails/blob/3-1-stable/activemodel/lib/active_model/validations/numericality.rb#L47

Dzięki : )