add explicit poll choice ordering and poll_session deletion

fixes CNVS-13521,CNVS-13489
This commit adds 3 things.  One is a fix for deleting a poll_session when
there are poll_submissions created already for the poll_session. The
second is the addition of a new attribute on poll_choices called
'position'.
The 'position' attribute specifies the order a poll choice should be
returned in. The third is the visibility of poll sessions to students:
as long as a student is enrolled in the course (and course section, if
it exists on the poll session) associated with the poll session, they
can access it, whether or not it's open or closed.

Test plans
1. Deleting a poll session
As a teacher
- Create a poll and related poll session
- Open the poll session for submissions
As a student
- Create a submission for the newly created poll session
As a teacher
- You should be able to delete the poll session

2. Position ordering attribute
As a teacher
- Create a poll
- Create poll choices, passing along the 'position' attribute to specify
  ordering (try to mix these up, e.g. create a poll choice with a
  position
  of 2, then a poll choice with an position of 4, then a poll choice with
  an position of 3, then a poll choice with an position of 1)
- Retrieve the poll choices for the poll with the poll choices index
  endpoint
- The poll should be returned in the order they were specified using the
  'position' attribute

3. Poll session visibility to students
As a teacher
- Create a poll
- Create an associated poll session with a valid course
As a student enrolled in the given course
- You should be able to view the poll session whether or not it's open
  or closed.
As a student not enrolled in the given course
- You should not be able to view the poll session, whether or not it's
  open or closed.

Change-Id: Ifabd64540d843c83f3a22fb6668c4fcac8f728b0
Reviewed-on: https://gerrit.instructure.com/36087
Reviewed-by: Derek DeVries <ddevries@instructure.com>
QA-Review: Trevor deHaan <tdehaan@instructure.com>
Product-Review: Bryan Madsen <bryan@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
Product-Review: Josh Simpson <jsimpson@instructure.com>
This commit is contained in:
Josh Simpson 2014-06-06 16:24:54 -06:00
parent 8b9e82ade8
commit 53920a96ce
10 changed files with 77 additions and 20 deletions

View File

@ -44,6 +44,11 @@ module Polling
# "description": "The text of the poll choice.",
# "type": "string",
# "example": "Choice A"
# },
# "position": {
# "description": "The order of the poll choice in relation to it's sibling poll choices.",
# "type": "integer",
# "example": 1
# }
# }
# }
@ -101,6 +106,9 @@ module Polling
# @argument poll_choices[][is_correct] [Optional, Boolean]
# Whether this poll choice is considered correct or not. Defaults to false.
#
# @argument poll_choices[][position] [Optional, Integer]
# The order this poll choice should be returned in the context it's sibling poll choices.
#
# @example_response
# {
# "poll_choices": [PollChoice]
@ -131,6 +139,9 @@ module Polling
# @argument poll_choices[][is_correct] [Optional, Boolean]
# Whether this poll choice is considered correct or not. Defaults to false.
#
# @argument poll_choices[][position] [Optional, Integer]
# The order this poll choice should be returned in the context it's sibling poll choices.
#
# @example_response
# {
# "poll_choices": [PollChoice]

View File

@ -23,7 +23,7 @@ module Polling
# @model PollSession
# {
# "id": "PollSession",
# "required": ["id", "poll_id", "course_id", "course_section_id"],
# "required": ["id", "poll_id", "course_id"],
# "properties": {
# "id": {
# "description": "The unique identifier for the poll session.",

View File

@ -21,7 +21,7 @@ module Polling
attr_accessible :user, :question, :description
belongs_to :user
has_many :poll_choices, class_name: 'Polling::PollChoice', dependent: :destroy
has_many :poll_choices, class_name: 'Polling::PollChoice', dependent: :destroy, order: :position
has_many :poll_submissions, class_name: 'Polling::PollSubmission', dependent: :destroy
has_many :poll_sessions, class_name: 'Polling::PollSession', dependent: :destroy

View File

@ -18,7 +18,9 @@
module Polling
class PollChoice < ActiveRecord::Base
attr_accessible :text, :poll, :is_correct
set_table_name 'polling_poll_choices'
attr_accessible :text, :poll, :is_correct, :position
belongs_to :poll, class_name: 'Polling::Poll'
has_many :poll_submissions, class_name: 'Polling::PollSubmission', dependent: :destroy

View File

@ -23,7 +23,7 @@ module Polling
belongs_to :course
belongs_to :course_section
belongs_to :poll, class_name: 'Polling::Poll'
has_many :poll_submissions, class_name: 'Polling::PollSubmission'
has_many :poll_submissions, class_name: 'Polling::PollSubmission', dependent: :destroy
validates_presence_of :poll, :course
validate :section_belongs_to_course
@ -34,11 +34,14 @@ module Polling
can :read and can :create and can :delete and can :publish
given do |user, session|
self.cached_context_grants_right?(user, session, :read) &&
self.is_published? &&
(self.course_section ? self.course_section.grants_right?(user, session, :read) : true)
self.visible_to?(user, session)
end
can :read and can :submit
can :read
given do |user, session|
self.visible_to?(user, session) && self.is_published?
end
can :submit
end
def self.available_for(user)
@ -65,6 +68,11 @@ module Polling
save!
end
def visible_to?(user, session)
self.cached_context_grants_right?(user, session, :read) &&
(self.course_section ? self.course_section.grants_right?(user, session, :read) : true)
end
private
def section_belongs_to_course
if self.course && self.course_section

View File

@ -2,7 +2,7 @@ module Polling
class PollChoiceSerializer < Canvas::APISerializer
root :poll_choice
attributes :id, :text, :is_correct
attributes :id, :text, :is_correct, :position
has_one :poll, embed: :id
@ -31,7 +31,7 @@ module Polling
end
def student_keys
keys = [:id, :text]
keys = [:id, :text, :position]
keys << :is_correct if poll.closed_and_viewable_for?(current_user)
keys
end

View File

@ -0,0 +1,11 @@
class AddPositionColumnToPollChoices < ActiveRecord::Migration
tag :predeploy
def self.up
add_column :polling_poll_choices, :position, :integer
end
def self.down
remove_column :polling_poll_choices, :position
end
end

View File

@ -26,9 +26,11 @@ describe Polling::PollChoicesController, type: :request do
describe 'GET index' do
before(:each) do
@poll = @teacher.polls.create!(question: "Example Poll")
5.times do |n|
@poll.poll_choices.create!(text: "Poll Choice #{n+1}", is_correct: false)
end
@poll.poll_choices.create!(text: "Poll Choice 1", is_correct:false, position: 1)
@poll.poll_choices.create!(text: "Poll Choice 3", is_correct:false, position: 3)
@poll.poll_choices.create!(text: "Poll Choice 4", is_correct:false, position: 4)
@poll.poll_choices.create!(text: "Poll Choice 2", is_correct:false, position: 2)
@poll.poll_choices.create!(text: "Poll Choice 5", is_correct:false, position: 5)
end
def get_index(raw = false, data = {})
@ -46,10 +48,20 @@ describe Polling::PollChoicesController, type: :request do
poll_choices_json.size.should == 5
poll_choices_json.each_with_index do |pc, i|
pc['text'].should == "Poll Choice #{5-i}"
pc['text'].should == "Poll Choice #{i+1}"
end
end
it "returns the poll choices in the correct order" do
json = get_index
poll_choices_json = json['poll_choices']
poll_choices_json.each_with_index do |pc, i|
pc['position'].should == i+1
end
end
context "as a student" do
before(:each) do
student_in_course(:active_all => true, :course => @course)
@ -159,12 +171,13 @@ describe Polling::PollChoicesController, type: :request do
context "as a teacher" do
it "creates a poll choice successfully" do
post_create(text: 'Poll Choice 1', is_correct: false)
post_create(text: 'Poll Choice 1', is_correct: false, position: 1)
@poll.poll_choices.first.text.should == 'Poll Choice 1'
@poll.poll_choices.first.position.should == 1
end
it "sets is_correct to false if is_correct is provided but blank" do
post_create(text: 'is correct poll choice', is_correct: '')
post_create(text: 'is correct poll choice', is_correct: '', position: 1)
@poll.poll_choices.first.text.should == 'is correct poll choice'
@poll.poll_choices.first.is_correct.should be_false
end

View File

@ -136,6 +136,20 @@ describe Polling::PollSessionsController, type: :request do
end
context "as a student" do
it "doesn't display if the student isn't enrolled in the associated course or course section" do
section = @course.course_sections.create!(name: 'Some Course Section')
@poll_session.course_section = section
@poll_session.save
student_in_course(active_all: true, course: @course)
get_show(true)
response.code.should == '401'
@poll_session.reload
@poll_session.poll_submissions.size.should be_zero
end
it "returns has_submitted as true if the student has made a submission" do
choice = @poll.poll_choices.create!(text: 'Choice A', is_correct: true)
submission = create_submission(choice)

View File

@ -66,9 +66,6 @@ describe Polling::PollsController, type: :request do
describe 'GET show' do
before(:each) do
@poll = @teacher.polls.create!(question: 'An Example Poll')
5.times do |n|
@poll.poll_choices.create!(text: "Poll Choice #{n+1}", is_correct: false)
end
end
def get_show(raw = false, data = {})
@ -114,7 +111,8 @@ describe Polling::PollsController, type: :request do
it "shouldn't return the id of the user that created the poll" do
student_in_course(:active_all => true, :course => @course)
@poll.poll_sessions.create!(course: @course)
session = @poll.poll_sessions.create!(course: @course)
session.publish!
json = get_show
poll_json = json['polls'].first