Rails: разрешить пользователям голосовать только один раз за пин

У меня есть система голосов в моем приложении rails, которая позволяет пользователям голосовать за пин. Но я хотел бы ограничить возможность голосовать за пин только один раз.

приложение/контроллеры/pins_controller.rb

  def upvote
    @pin = Pin.find(params[:id])
    @pin.votes.create
    redirect_to(pins_path)
  end

приложение/models/pin.rb

class Pin < ActiveRecord::Base

    belongs_to :user

    has_many :votes, dependent: :destroy

    has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }
    has_attached_file :logo, :styles => { :medium => "300x300>", :thumb => "100x100>" }

    end

app/config/routes.rb

  resources :pins do
  member do
    post 'upvote'
  end
end

Я не уверен, как это реализовать, поскольку я пытался внедрить систему, позволяющую пользователям голосовать только один раз, это не то, чего я хочу, я хочу, чтобы они могли голосовать за «ПИН-код» только один раз. Я знаю, что гем acts_as_votable предоставляет эту функцию, но, поскольку я ее не использую, я хотел знать, есть способ реализовать это в моем собственном коде.

Любые идеи?

ОБНОВЛЕНИЕ: этот метод допускает только один голос на пин. см. решение @Ege

заставить его работать с этим:

def upvote
  @pin = Pin.find(params[:id])

  if @pin.votes.count == 0
     @pin.votes.create
     redirect_to(pins_path)
  else flash[:notice] =  "You have already upvote this!"
    redirect_to(pins_path)
end
end

person zacchj    schedule 06.07.2014    source источник


Ответы (2)


Вы выбрали ответ Beautifulcoder как правильный, но вы должны знать, что он потенциально неверен и может быть неочевидным, если вы новичок в Rails.

Вы говорите, что пин должен иметь только один голос, но, по-видимому, вы имеете в виду, что он должен иметь один голос на пользователя, то есть каждый пользователь должен иметь возможность проголосовать за пин только один раз. Так обычно работают механизмы голосования.

С ответом Beautifulcoder, если я проголосую за пин, вы не сможете проголосовать за него, потому что ваш контроллер подсчитает количество голосов за пин, вернет 1 (потому что я проголосовал за него) и не позволит вам проголосовать. Кроме того, появится сообщение о том, что вы проголосовали за него, а на самом деле нет!

Как решить эту проблему? К счастью, Rails делает это очень просто. Ваш голос на самом деле является замаскированной моделью присоединения. Он устанавливает отношения (т. е. ассоциации) между пользователями и выводами. Пользователь может проголосовать за пин, а за пин может проголосовать пользователь. Другими словами, голоса «соединяют» пользователей и пины! Что вам нужно сделать, так это определить эту связь, используя ассоциации ActiveRecord.

В вашу модель Pin будет добавлена ​​эта ассоциация:

class Pin < ActiveRecord::Base

  has_many :votes, dependent: :destroy
  has_many :upvoted_users, through: :votes, source: :user

  ...

end

Это позволяет вам делать такие вещи, как @pin.upvoted_users, и получать список пользователей, которые проголосовали за этот пин. Довольно неплохо, если вы хотите иметь возможность уведомить владельца булавки!

Вы также хотите добавить обратную ассоциацию к вашей модели пользователя:

class User < ActiveRecord::Base

  has_many :votes, dependent: :destroy
  has_many :upvoted_pins, through: :votes, source: :pin

  ...

end

А затем измените модель Vote следующим образом:

class Vote < ActiveRecord::Base

  belongs_to :user
  belongs_to :pin
  validates_uniqueness_of :pin_id, scope: :user_id

end

И, наконец, в вашем контроллере вы должны сделать:

def upvote
  @pin = Pin.find(params[:id])

  if @pin.votes.create(user_id: current_user.id)
    flash[:notice] =  "Thank you for upvoting!"
    redirect_to(pins_path)
  else 
    flash[:notice] =  "You have already upvoted this!"
    redirect_to(pins_path)
  end
end

Вуаля! Теперь у вас есть решение, в котором пользователи могут голосовать за элементы, но только один раз за каждый элемент.

person Ege Ersoz    schedule 06.07.2014
comment
Здравствуйте @Ege, я очень ценю ваш ответ, это очень мило с вашей стороны. Дело в том, что с вашим методом, когда я пытался проголосовать за пин, я столкнулся с ошибкой: неизвестный атрибут: user_id - person zacchj; 06.07.2014
comment
Если в вашей модели голосования нет целого числа user_id, вам нужно добавить его с помощью миграции. Это имеет смысл, если подумать: голоса принадлежат пользователям, и поэтому их модель должна иметь внешний ключ user_id. - person Ege Ersoz; 07.07.2014
comment
Это правильный ответ. Я бы создал метод голосования для модели User и вызвал current_user.vote(@pin), чтобы его было легче понять. - person Wilson Silva; 07.07.2014
comment
@Ege Спасибо, я добавил целое число user_id в свой столбец голосов, и теперь он работает хорошо. Единственная проблема связана с флеш-сообщением - когда я много раз голосую, голосование принимается только один раз, чего я и хочу, - но флеш-сообщение остается прежним, даже если оно не голосует, потому что пользователь уже сделал это, он получил флэш-сообщение: спасибо за голосование - person zacchj; 07.07.2014

небольшое обновление условного действия контроллера

def upvote
  @pin = Pin.find(params[:id])
  @pin.votes.create(user_id: current_user.id)

  if @pin.save
    flash[:notice] =  "Thank you for upvoting!"
    redirect_to(pins_path)
  else 
    flash[:notice] =  "You have already upvoted this!"
    redirect_to(pins_path)
  end
end
person Thomas Roest    schedule 08.04.2016