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

#ruby-on-rails #ruby #vote #pins

#ruby-on-rails #ruby #Голосовать #выводы

Вопрос:

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

app /controllers/pins_controller.rb

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

app /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
  

Я не уверен, как это реализовать, поскольку я пытался внедрить систему, которая позволяла пользователям повышать голос только один раз, это не то, что я хочу, я хочу, чтобы они могли повышать голос за «PIN» только один раз.
Я знаю, что драгоценный камень 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
  

Ответ №1:

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

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

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

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

В вашей модели выводов была бы добавлена эта ассоциация:

 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
  

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

 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
  

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

Комментарии:

1. Привет @Ege Я действительно ценю ваш ответ, это действительно любезно с вашей стороны. Дело в том, что с вашим методом, когда я пытался повысить голос за пин-код, я получал сообщение об ошибке: неизвестный атрибут: user_id

2. Если в вашей модели голосования нет целого числа user_id, вам нужно добавить его при переносе. Это имеет смысл, если вы подумаете об этом: голоса принадлежат пользователям, и, следовательно, их модель должна иметь внешний ключ user_id.

3. Это правильный ответ. Я бы создал метод голосования в пользовательской модели и вызвал current_user.vote (@pin), чтобы упростить понимание.

4. @Ege Спасибо, я добавил целое число user_id в свой столбец голосов, и теперь оно работает хорошо. Единственная проблема связана с флэш-сообщением — когда я повышаю голос много раз, повышающие голоса принимаются только один раз, чего я и хочу, но флэш-сообщение остается неизменным, даже если оно не повышает голос, потому что пользователь уже сделал это, он получил флэш-сообщение: «спасибо за повышение»

Ответ №2:

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

 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