add low_level_locked_for

refs RECNVS-409

The return value of `locked_for?` is trash, but cannot be changed since
it is returned directly by the api. This commit introduces
`low_level_locked_for?` methods with a more useful return value.

Test plan: specs pass

Change-Id: I2e5614990ec15d9e7073c72faabe14a64638f4fb
Reviewed-on: https://gerrit.instructure.com/150246
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
Tested-by: Jenkins
QA-Review: Collin Parrish <cparrish@instructure.com>
Product-Review: Cameron Matheson <cameron@instructure.com>
This commit is contained in:
Cameron Matheson 2018-05-14 16:29:01 -06:00
parent 7e70b17d1a
commit 53360e5bc7
7 changed files with 61 additions and 26 deletions

View File

@ -36,6 +36,7 @@ class Assignment < ActiveRecord::Base
include TurnitinID
include Plannable
include DuplicatingObjects
include LockedFor
ALLOWED_GRADING_TYPES = %w(points percent letter_grade gpa_scale pass_fail not_graded).freeze
@ -1235,22 +1236,24 @@ class Assignment < ActiveRecord::Base
])
end
def locked_for?(user, opts={})
def low_level_locked_for?(user, opts={})
return false if opts[:check_policies] && context.grants_right?(user, :read_as_admin)
Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
locked = false
assignment_for_user = self.overridden_for(user)
if (assignment_for_user.unlock_at && assignment_for_user.unlock_at > Time.zone.now)
locked = {:asset_string => assignment_for_user.asset_string, :unlock_at => assignment_for_user.unlock_at}
locked = {object: assignment_for_user, unlock_at: assignment_for_user.unlock_at}
elsif self.could_be_locked && item = locked_by_module_item?(user, opts)
locked = {:asset_string => self.asset_string, :context_module => item.context_module.attributes}
locked = {object: self, module: item.context_module}
elsif (assignment_for_user.lock_at && assignment_for_user.lock_at < Time.zone.now)
locked = {:asset_string => assignment_for_user.asset_string, :lock_at => assignment_for_user.lock_at, :can_view => true}
locked = {object: assignment_for_user, lock_at: assignment_for_user.lock_at, can_view: true}
else
each_submission_type do |submission, _, short_type|
next unless self.send("#{short_type}?")
if submission_locked = submission.locked_for?(user, opts.merge(:skip_assignment => true))
if submission_locked = submission.low_level_locked_for?(user, opts.merge(:skip_assignment => true))
locked = submission_locked
end
break
end

View File

@ -20,6 +20,7 @@ class ContextModule < ActiveRecord::Base
include Workflow
include SearchTermHelper
include DuplicatingObjects
include LockedFor
include MasterCourses::Restrictor
restrict_columns :state, [:workflow_state]
@ -320,11 +321,11 @@ class ContextModule < ActiveRecord::Base
can :read
end
def locked_for?(user, opts={})
def low_level_locked_for?(user, opts={})
return false if self.grants_right?(user, :read_as_admin)
available = self.available_for?(user, opts)
return {:asset_string => self.asset_string, :context_module => self.attributes} unless available
return {:asset_string => self.asset_string, :context_module => self.attributes, :unlock_at => self.unlock_at} if self.to_be_unlocked
return {object: self, module: self} unless available
return {object: self, module: self, unlock_at: unlock_at} if to_be_unlocked
false
end

View File

@ -34,6 +34,7 @@ class DiscussionTopic < ActiveRecord::Base
include Plannable
include MasterCourses::Restrictor
include DuplicatingObjects
include LockedFor
restrict_columns :content, [:title, :message]
restrict_columns :settings, [:delayed_post_at, :require_initial_post, :discussion_type,
@ -1322,25 +1323,25 @@ class DiscussionTopic < ActiveRecord::Base
end
end
# Public: Determine if the discussion topic is locked for a specific user. The topic is locked when the
# Determine if the discussion topic is locked for a specific user. The topic is locked when the
# delayed_post_at is in the future or the group assignment is locked. This does not determine
# the visibility of the topic to the user, only that they are unable to reply.
def locked_for?(user, opts={})
def low_level_locked_for?(user, opts={})
return false if opts[:check_policies] && self.grants_right?(user, :read_as_admin)
Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
locked = false
if (self.delayed_post_at && self.delayed_post_at > Time.now)
locked = {:asset_string => self.asset_string, :unlock_at => self.delayed_post_at}
locked = {object: self, unlock_at: delayed_post_at}
elsif (self.lock_at && self.lock_at < Time.now)
locked = {:asset_string => self.asset_string, :lock_at => self.lock_at, :can_view => true}
elsif !opts[:skip_assignment] && (self.assignment && l = self.assignment.locked_for?(user, opts))
locked = {object: self, lock_at: lock_at, can_view: true}
elsif !opts[:skip_assignment] && (assignment && l = assignment.low_level_locked_for?(user, opts))
locked = l
elsif self.could_be_locked && item = locked_by_module_item?(user, opts)
locked = {:asset_string => self.asset_string, :context_module => item.context_module.attributes}
locked = {object: self, module: item.context_module}
elsif self.locked? # nothing more specific, it's just locked
locked = {:asset_string => self.asset_string, :can_view => true}
elsif (self.root_topic && l = self.root_topic.locked_for?(user, opts))
locked = {object: self, can_view: true}
elsif (root_topic && l = root_topic.low_level_locked_for?(user, opts))
locked = l
end
locked

View File

@ -30,6 +30,7 @@ class Quizzes::Quiz < ActiveRecord::Base
include SearchTermHelper
include Plannable
include Canvas::DraftStateValidations
include LockedFor
attr_readonly :context_id, :context_type
attr_accessor :notify_of_update
@ -752,8 +753,7 @@ class Quizzes::Quiz < ActiveRecord::Base
alias_method :to_s, :quiz_title
def locked_for?(user, opts={})
def low_level_locked_for?(user, opts={})
::Rails.cache.fetch(locked_cache_key(user), :expires_in => 1.minute) do
user_submission = user && quiz_submissions.where(user_id: user.id).first
return false if user_submission && user_submission.manually_unlocked
@ -764,7 +764,7 @@ class Quizzes::Quiz < ActiveRecord::Base
lock_time_already_occurred = quiz_for_user.lock_at && quiz_for_user.lock_at <= Time.zone.now
locked = false
lock_info = { asset_string: asset_string }
lock_info = { object: quiz_for_user }
if unlock_time_not_yet_reached
locked = lock_info.merge({ unlock_at: quiz_for_user.unlock_at })
elsif lock_time_already_occurred
@ -772,19 +772,19 @@ class Quizzes::Quiz < ActiveRecord::Base
elsif !opts[:skip_assignment] && (assignment_lock = locked_by_assignment?(user, opts))
locked = assignment_lock
elsif (module_lock = locked_by_module_item?(user, opts))
locked = lock_info.merge({ context_module: module_lock.context_module.attributes })
locked = lock_info.merge({module: module_lock.context_module})
elsif !context.try_rescue(:is_public) && !context.grants_right?(user, :participate_as_student) && !opts[:is_observer]
locked = lock_info.merge({ missing_permission: :participate_as_student.to_s })
end
locked
locked
end
end
def locked_by_assignment?(user, opts = {})
return false unless for_assignment?
assignment.locked_for?(user, opts)
assignment.low_level_locked_for?(user, opts)
end
def clear_locked_cache(user)

View File

@ -34,6 +34,7 @@ class WikiPage < ActiveRecord::Base
include Plannable
include DuplicatingObjects
include SearchTermHelper
include LockedFor
include MasterCourses::Restrictor
restrict_columns :content, [:body, :title]
@ -233,13 +234,14 @@ class WikiPage < ActiveRecord::Base
scope :order_by_id, -> { order(:id) }
def locked_for?(user, opts={})
def low_level_locked_for?(user, opts={})
return false unless self.could_be_locked
Rails.cache.fetch([locked_cache_key(user), opts[:deep_check_if_needed]].cache_key, :expires_in => 1.minute) do
locked = false
if item = locked_by_module_item?(user, opts)
locked = {:asset_string => self.asset_string, :context_module => item.context_module.attributes}
locked[:unlock_at] = locked[:context_module]["unlock_at"] if locked[:context_module]["unlock_at"] && locked[:context_module]["unlock_at"] > Time.now.utc
locked = {object: self, :module => item.context_module}
unlock_at = locked[:module].unlock_at
locked[:unlock_at] = unlock_at if unlock_at && unlock_at > Time.now.utc
end
locked
end

View File

@ -44,7 +44,7 @@ module HasContentTags
end
def locked_cache_key(user)
keys = ['_locked_for3', self, user]
keys = ['_locked_for4', self, user]
unlocked_at = self.respond_to?(:unlock_at) ? self.unlock_at : nil
locked_at = self.respond_to?(:lock_at) ? self.lock_at : nil
keys << (unlocked_at ? unlocked_at > Time.zone.now : false)

28
lib/locked_for.rb Normal file
View File

@ -0,0 +1,28 @@
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module LockedFor
def locked_for?(user, opts={})
lock_info = low_level_locked_for?(user, opts).dup
return false if lock_info == false
lock_info[:asset_string] = lock_info.delete(:object).asset_string
lock_info[:context_module] = lock_info.delete(:module).attributes if lock_info.key?(:module)
lock_info
end
end