Walidacja - możliwość jednkorotnego dodania wartości do bazy; self

Witam!

Chciałbym, aby użytkownik miał możliwość tylko jednokrotnego zagłosowania na dany post. Napisałem prostą walidację, lecz skrypt zawsze “przepuszcza” dane.

EDIT: Dobra, poradziłem sobie. Dopiero teraz dotarło do mnie, że używanie tu self nie ma dużego sensu.

EEDIT2: Napisałem tak i działa, lecz sposób nie dokońca mnie satysfakcjonuje… errors.add_to_base('error message') if (Vote.find(:all, :conditions => {:post_id => post.id, :user_id => user.id} ).count > 0)

[code=ruby]class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :post

validate :something

private

def something
errors.add_to_base(‘error message’) if user.votes.include?(self) # tu leży problem, gdy zmienię podaną wartość na Vote.first albo last waliduje prawidłowo
end
end[/code]

[code=ruby]@post = Post.find(params[:id])
@vote = @post.votes.new(:user_id => current_user.id)

if @vote.save
  flash[:notice] = "Successfully added vote."
else
  flash[:error] = "Vote was not added."
end
redirect_to :back[/code]

Processing VotesController#new (for 127.0.0.1 at 2009-11-17 20:34:13) [GET] Parameters: {"action"=>"new", "id"=>"3", "controller"=>"votes"} Post Load (0.5ms) SELECT * FROM "posts" WHERE ("posts"."id" = 3) User Load (0.5ms) SELECT * FROM "users" WHERE ("users"."id" = 2) CACHE (0.0ms) SELECT * FROM "users" WHERE ("users"."id" = 2) Vote Load (0.2ms) SELECT "votes".id FROM "votes" WHERE ("votes"."id" = NULL) AND ("votes".user_id = 2) LIMIT 1 # dlaczego - "votes"."id" = NULL Vote Create (0.5ms) INSERT INTO "votes" ("created_at", "updated_at", "post_id", "user_id") VALUES('2009-11-17 19:34:14', '2009-11-17 19:34:14', 3, 2) Redirected to http://localhost:3000/posts/3

[code=ruby]class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :post

validate_uniqueness_of :user, :scope => :post_id
end

class Post < ActiveRecord::Base
has_many :votes

def vote!(user)
votes.build(:user => user).save
end
end

w kontrolerze

@post = Post.find(params[:id])
if @post.vote!(current_user)

flasz

else

flasz…

end[/code]

Dzięki, nie znałem tej metody.

Jednak, zwraca błąd:

NoMethodError (You have a nil object when you didn't expect it! The error occurred while evaluating nil.text?): app/models/post.rb:20:in `vote'
Gdzie 20. linia to build.

Daj troche wiecej z tego stack trace i pokaż model Post

wiecej walidacji tutaj: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html

Poprawiłem, powinno być

validates_uniqueness_of :user_id, :scope => :post_id

W każdym razie dzięki

[quote=sevos][code=ruby]class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :post

validate_uniqueness_of :user, :scope => :post_id
end

class Post < ActiveRecord::Base
has_many :votes

def vote!(user)
votes.build(:user => user).save
end
end

w kontrolerze

@post = Post.find(params[:id])
if @post.vote!(current_user)

flasz

else

flasz…

end[/code]
[/quote]
Kilka drobnych uwag:

To nie idzie w transakcji, nie ma blokowania… więc spokojnie będzie można zagłosować kilka razy. 10 wątków odczyta jednocześnie dane… stwierdzi, że nie ma głosu i potem zagłosuje. Jedyne rozwiązanie: zrobienie porządnie bazy danych (dodanie unique(user, post) dzięki czemu kolejne inserty się wywalą) oraz obsługa tego, że insert się nie uda w modelu żeby powiadomić użytkownika.

Coś mi świta, że zgodnie z konwencją Rubiego funkcja vote! powinna być tylko specjalną wersją funkcji vote, a takiej nie ma.

Cytując http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html:

Jedyną konwencją jaką znam w tym obszarze to, że ! używa się do oznaczania potencjalnie niebezpiecznych metod, rzucających wyjątek lub modyfikujących obiekt in-place (zamiast zwracania bezpiecznej kopii, patrz kolekcje).

Zawsze możesz użyć gotowego rozwiązania - vote_fu z drobnymi poprawkami.

W ogóle przydałby się dobry gem do głosowania, bo vote_fu niby działa, ale po instalacji trzeba zmienić kilka plików, nie mówiąc już o późniejszym dostosowaniu tego pluginu do działania z postgresem…