discourse/plugins/poll/lib/polls_updater.rb
Guo Xiang Tan a849fae5ee Add specs for polls (#4246)
* Extract validation logic into a service object.

* Extract logic for updating polls custom fields into a service object.

* Use `strip_heredoc` instead.

* FIX: Polls do not update when configuration has been changed.
2016-06-06 17:04:56 +02:00

95 lines
3.5 KiB
Ruby

module DiscoursePoll
class PollsUpdater
VALID_POLLS_CONFIGS = %w{type min max}.map(&:freeze)
def self.update(post, polls)
# load previous polls
previous_polls = post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] || {}
# extract options
current_options = extract_option_ids(polls)
previous_options = extract_option_ids(previous_polls)
# are the polls different?
if polls_updated?(polls, previous_polls) || (current_options != previous_options)
has_votes = total_votes(previous_polls) > 0
# outside of the 5-minute edit window?
if post.created_at < 5.minutes.ago && has_votes
# cannot add/remove/rename polls
if polls.keys.sort != previous_polls.keys.sort
post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes"))
return
end
# deal with option changes
if User.staff.pluck(:id).include?(post.last_editor_id)
# staff can only edit options
polls.each_key do |poll_name|
if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size && previous_polls[poll_name]["voters"].to_i > 0
post.errors.add(:base, I18n.t("poll.staff_cannot_add_or_remove_options_after_5_minutes"))
return
end
end
else
# OP cannot edit poll options
post.errors.add(:base, I18n.t("poll.op_cannot_edit_options_after_5_minutes"))
return
end
end
# try to merge votes
polls.each_key do |poll_name|
next unless previous_polls.has_key?(poll_name)
# when the # of options has changed, reset all the votes
if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size
PostCustomField.where(post_id: post.id, name: DiscoursePoll::VOTES_CUSTOM_FIELD).destroy_all
post.clear_custom_fields
next
end
polls[poll_name]["voters"] = previous_polls[poll_name]["voters"]
polls[poll_name]["anonymous_voters"] = previous_polls[poll_name]["anonymous_voters"] if previous_polls[poll_name].has_key?("anonymous_voters")
for o in 0...polls[poll_name]["options"].size
current_option = polls[poll_name]["options"][o]
previous_option = previous_polls[poll_name]["options"][o]
current_option["votes"] = previous_option["votes"]
current_option["anonymous_votes"] = previous_option["anonymous_votes"] if previous_option.has_key?("anonymous_votes")
end
end
# immediately store the polls
post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] = polls
post.save_custom_fields(true)
# publish the changes
MessageBus.publish("/polls/#{post.topic_id}", { post_id: post.id, polls: polls })
end
end
def self.polls_updated?(current_polls, previous_polls)
return true if (current_polls.keys.sort != previous_polls.keys.sort)
current_polls.each_key do |poll_name|
if !previous_polls[poll_name] ||
(current_polls[poll_name].values_at(*VALID_POLLS_CONFIGS) != previous_polls[poll_name].values_at(*VALID_POLLS_CONFIGS))
return true
end
end
false
end
def self.extract_option_ids(polls)
polls.values.map { |p| p["options"].map { |o| o["id"] } }.flatten.sort
end
def self.total_votes(polls)
polls.map { |key, value| value["voters"].to_i }.sum
end
end
end