diff --git a/app/messages/forgot_password.email.erb b/app/messages/forgot_password.email.erb index f559969fa8e..3536660582b 100644 --- a/app/messages/forgot_password.email.erb +++ b/app/messages/forgot_password.email.erb @@ -6,18 +6,20 @@ <%= t('subject', 'Forgot Password: Canvas') %> <% end %> +<% pseudonyms = asset.user.all_active_pseudonyms %> +<% first_pseudonym = pseudonyms.first %> <%= t('requested_password_reset', 'You requested a confirmation of your password for logging into Canvas.') %> -<% if asset.active_pseudonyms.length > 1 %><%= before_label('associated_with_accounts', 'This address is associated with the following accounts') %> +<% if pseudonyms.length > 1 %><%= before_label('associated_with_accounts', 'This address is associated with the following accounts') %> -<% asset.active_pseudonyms.each do |p| %> +<% pseudonyms.each do |p| %> - <%= t('login_at_account', '%{login_email} at %{account_name}', :login_email => p.unique_id, :account_name => p.account.display_name) %> <% if p.managed_password? %><%= t('login_managed_by_account', "this login's credentials are managed by %{account_name}", :account_name => p.account.name) %><% else %><%= before_label('change_password_at', "change this login's password at") %> <%= HostUrl.protocol %>://<%= HostUrl.context_host((p.account rescue nil)) %>/pseudonyms/<%= p.id %>/change_password/<%= (p.communication_channel || asset).confirmation_code %><% end %> <% end %> <% else %> -<%= t('associated_login', 'This address is associated with the login, "%{login_identifier}".', :login_identifier => asset.active_pseudonyms.first.unique_id) %><% if asset.active_pseudonyms.first.managed_password? %> <%= t('password_from_account', "The password for this login should have been given to you by the system administrators at %{account_name}, and Instructure doesn't have access to your password. If your password is not working, please contact the system administrators about changing or verifying your password.", :account_name => asset.active_pseudonyms.first.account.display_name) %><% else %> +<%= t('associated_login', 'This address is associated with the login, "%{login_identifier}".', :login_identifier => first_pseudonym.unique_id) %><% if first_pseudonym.managed_password? %> <%= t('password_from_account', "The password for this login should have been given to you by the system administrators at %{account_name}, and Instructure doesn't have access to your password. If your password is not working, please contact the system administrators about changing or verifying your password.", :account_name => first_pseudonym.account.display_name) %><% else %> <%= before_label('click_to_reset', 'To set a new password, please click the following link') %> -<%= HostUrl.protocol %>://<%= HostUrl.context_host((asset.active_pseudonyms.first.account rescue nil)) %>/pseudonyms/<%= asset.active_pseudonyms.first.id %>/change_password/<%= asset.confirmation_code %><% end %> +<%= HostUrl.protocol %>://<%= HostUrl.context_host((first_pseudonym.account rescue nil)) %>/pseudonyms/<%= first_pseudonym.id %>/change_password/<%= asset.confirmation_code %><% end %> <% end %> diff --git a/app/messages/forgot_password.email.html.erb b/app/messages/forgot_password.email.html.erb index 6da472f3dfd..6bc50f23a48 100644 --- a/app/messages/forgot_password.email.html.erb +++ b/app/messages/forgot_password.email.html.erb @@ -6,10 +6,12 @@ <% end %>

<%= t('requested_password_reset', 'You requested a confirmation of your password for logging into Canvas.') %>

-<% if asset.active_pseudonyms.length > 1 -%> +<% pseudonyms = asset.user.all_active_pseudonyms %> +<% first_pseudonym = pseudonyms.first %> +<% if pseudonyms.length > 1 -%>

<%= before_label('associated_with_accounts', 'This address is associated with the following accounts') %>

- <% asset.active_pseudonyms.each do |p| %> + <% pseudonyms.each do |p| %>

- <%= t('login_at_account', '%{login_email} at %{account_name}', :login_email => p.unique_id, :account_name => p.account.display_name) %>
<% if p.managed_password? %> <%= t('login_managed_by_account', "this login's credentials are managed by %{account_name}", :account_name => p.account.name) %> @@ -20,9 +22,9 @@

<% end %> <% else %> -

<%= t('associated_login', 'This address is associated with the login, "%{login_identifier}".', :login_identifier => asset.active_pseudonyms.first.unique_id) %>

- <% if asset.active_pseudonyms.first.managed_password? %> -

<%= t('password_from_account', "The password for this login should have been given to you by the system administrators at %{account_name}, and Instructure doesn't have access to your password. If your password is not working, please contact the system administrators about changing or verifying your password.", :account_name => asset.active_pseudonyms.first.account.display_name) %>

+

<%= t('associated_login', 'This address is associated with the login, "%{login_identifier}".', :login_identifier => first_pseudonym.unique_id) %>

+ <% if first_pseudonym.managed_password? %> +

<%= t('password_from_account', "The password for this login should have been given to you by the system administrators at %{account_name}, and Instructure doesn't have access to your password. If your password is not working, please contact the system administrators about changing or verifying your password.", :account_name => first_pseudonym.account.display_name) %>

<% else %> -

<%= t('click_to_reset', 'Click here to set a new password') %><% end %> +

<%= t('click_to_reset', 'Click here to set a new password') %><% end %> <% end %> diff --git a/app/models/account.rb b/app/models/account.rb index 1b4fbb94362..11b4c5f92a6 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -513,29 +513,32 @@ class Account < ActiveRecord::Base end def account_chain(opts = {}) - res = [self] - - if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql' - self.shard.activate do - res.concat Account.find_by_sql(<<-SQL) if self.parent_account_id - WITH RECURSIVE t AS ( - SELECT * FROM accounts WHERE id=#{self.parent_account_id} - UNION - SELECT accounts.* FROM accounts INNER JOIN t ON accounts.id=t.parent_account_id - ) - SELECT * FROM t - SQL - end - else - account = self - while account.parent_account - account = account.parent_account - res << account + unless @account_chain + res = [self] + if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql' + self.shard.activate do + res.concat Account.find_by_sql(<<-SQL) if self.parent_account_id + WITH RECURSIVE t AS ( + SELECT * FROM accounts WHERE id=#{self.parent_account_id} + UNION + SELECT accounts.* FROM accounts INNER JOIN t ON accounts.id=t.parent_account_id + ) + SELECT * FROM t + SQL + end + else + account = self + while account.parent_account + account = account.parent_account + res << account + end end + res << self.root_account unless res.map(&:id).include?(self.root_account_id) + @account_chain = res.compact end - res << self.root_account unless res.map(&:id).include?(self.root_account_id) - res << Account.site_admin if opts[:include_site_admin] && !self.site_admin? - res.compact + results = @account_chain.dup + results << Account.site_admin if opts[:include_site_admin] && !self.site_admin? + results end def account_chain_loop @@ -583,7 +586,6 @@ class Account < ActiveRecord::Base def account_chain_ids(opts={}) account_chain(opts).map(&:id) end - memoize :account_chain_ids def membership_for_user(user) self.account_users.find_by_user_id(user && user.id) @@ -951,17 +953,14 @@ class Account < ActiveRecord::Base def course_count self.child_courses.not_deleted.count('DISTINCT course_id') end - memoize :course_count - + def sub_account_count self.sub_accounts.active.count end - memoize :sub_account_count def user_count self.user_account_associations.count end - memoize :user_count def current_sis_batch if (current_sis_batch_id = self.read_attribute(:current_sis_batch_id)) && current_sis_batch_id.present? @@ -970,10 +969,11 @@ class Account < ActiveRecord::Base end def turnitin_settings + return @turnitin_settings if defined?(@turnitin_settings) if self.turnitin_account_id.present? && self.turnitin_shared_secret.present? - [self.turnitin_account_id, self.turnitin_shared_secret, self.turnitin_host] + @turnitin_settings = [self.turnitin_account_id, self.turnitin_shared_secret, self.turnitin_host] else - self.parent_account.try(:turnitin_settings) + @turnitin_settings = self.parent_account.try(:turnitin_settings) end end diff --git a/app/models/asset_user_access.rb b/app/models/asset_user_access.rb index 00f3f88933a..6788e28e060 100644 --- a/app/models/asset_user_access.rb +++ b/app/models/asset_user_access.rb @@ -150,7 +150,6 @@ class AssetUserAccess < ActiveRecord::Base asset = Context.find_asset_by_asset_string(asset_code, context) asset end - memoize :asset def asset_class_name self.asset.class.name.underscore if self.asset diff --git a/app/models/attachment.rb b/app/models/attachment.rb index a18c1234cb0..d37d530b24a 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -365,31 +365,25 @@ class Attachment < ActiveRecord::Base # or just some_attachment.scribd_thumbnail #will give you the default tumbnail for the document. def scribd_thumbnail(options={}) return unless self.scribd_doc && ScribdAPI.enabled? - if options.empty? && self.cached_scribd_thumbnail + if options.empty? + # we cache the 'default' version in the DB + unless self.cached_scribd_thumbnail + self.cached_scribd_thumbnail = self.request_scribd_thumbnail(options) + Attachment.where(:id => self).update_all(:cached_scribd_thumbnail => self.cached_scribd_thumbnail) + end self.cached_scribd_thumbnail else - begin - # if we aren't requesting special demensions, fetch and save it to the db. - if options.empty? - Scribd::API.instance.user = scribd_user - self.cached_scribd_thumbnail = self.scribd_doc.thumbnail(options) - # just update the cached_scribd_thumbnail column of this attachment without running callbacks - Attachment.where(:id => self).update_all(:cached_scribd_thumbnail => self.cached_scribd_thumbnail) - self.cached_scribd_thumbnail - else - Rails.cache.fetch(['scribd_thumb', self, options].cache_key) do - Scribd::API.instance.user = scribd_user - self.scribd_doc.thumbnail(options) - end - end - rescue Scribd::NotReadyError - nil - rescue => e - nil + # we cache other versions in the rails cache + Rails.cache.fetch(['scribd_thumb', self, options].cache_key) do + self.request_scribd_thumbnail(options) end end end - memoize :scribd_thumbnail + + def request_scribd_thumbnail(options) + Scribd::API.instance.user = scribd_user + self.scribd_doc.thumbnail(options) + end def turnitinable? self.content_type && [ @@ -860,7 +854,6 @@ class Attachment < ActiveRecord::Base # "still need to handle things that are not images with thumbnails, scribd_docs, or kaltura docs" end end - memoize :thumbnail_url def thumbnail_for_size(geometry) if self.class.allows_thumbnails_of_size?(geometry) @@ -1214,9 +1207,9 @@ class Attachment < ActiveRecord::Base end def hidden? - self.file_state == 'hidden' || (self.folder && self.folder.hidden?) + return @hidden if defined?(@hidden) + @hidden = self.file_state == 'hidden' || (self.folder && self.folder.hidden?) end - memoize :hidden? def published?; !locked?; end @@ -1227,7 +1220,6 @@ class Attachment < ActiveRecord::Base def public? self.file_state == 'public' end - memoize :public? def currently_locked self.locked || (self.lock_at && Time.now > self.lock_at) || (self.unlock_at && Time.now < self.unlock_at) || self.file_state == 'hidden' diff --git a/app/models/communication_channel.rb b/app/models/communication_channel.rb index a71fa816cf1..ec08230b747 100644 --- a/app/models/communication_channel.rb +++ b/app/models/communication_channel.rb @@ -119,11 +119,6 @@ class CommunicationChannel < ActiveRecord::Base p.context { @root_account } end - def active_pseudonyms - self.user.pseudonyms.active - end - memoize :active_pseudonyms - def uniqueness_of_path return if path.nil? return if retired? diff --git a/app/models/conversation_participant.rb b/app/models/conversation_participant.rb index 643e55e6f79..cbd51d46be0 100644 --- a/app/models/conversation_participant.rb +++ b/app/models/conversation_participant.rb @@ -233,22 +233,27 @@ class ConversationParticipant < ActiveRecord::Base :include_indirect_participants => false }.merge(options) - participants = conversation.participants - if options[:include_indirect_participants] - user_ids = - messages.map(&:all_forwarded_messages).flatten.map(&:author_id) | - messages.map{ - |m| m.submission.submission_comments.map(&:author_id) if m.submission - }.compact.flatten - user_ids -= participants.map(&:id) - participants += Shackles.activate(:slave) { MessageableUser.available.where(:id => user_ids).all } + shard.activate do + Rails.cache.fetch([conversation, user, 'participants', options].cache_key) do + participants = conversation.participants + if options[:include_indirect_participants] + user_ids = + messages.map(&:all_forwarded_messages).flatten.map(&:author_id) | + messages.map{ + |m| m.submission.submission_comments.map(&:author_id) if m.submission + }.compact.flatten + user_ids -= participants.map(&:id) + participants += Shackles.activate(:slave) { MessageableUser.available.where(:id => user_ids).all } + end + if options[:include_participant_contexts] + # we do this to find out the contexts they share with the user + user.load_messageable_users(participants, :strict_checks => false) + else + participants + end + end end - return participants unless options[:include_participant_contexts] - - # we do this to find out the contexts they share with the user - user.load_messageable_users(participants, :strict_checks => false) end - memoize :participants def properties(latest = last_message) properties = [] diff --git a/app/models/course.rb b/app/models/course.rb index c9a45dad254..3b962d7585e 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -542,7 +542,6 @@ class Course < ActiveRecord::Base user.cached_current_enrollments.any? { |e| e.course_id == self.id && e.participating_instructor? } end end - memoize :user_is_instructor? def user_is_student?(user, opts = {}) return unless user @@ -552,7 +551,6 @@ class Course < ActiveRecord::Base } end end - memoize :user_is_student? def user_has_been_instructor?(user) return unless user @@ -561,7 +559,6 @@ class Course < ActiveRecord::Base self.instructor_enrollments.active.find_by_user_id(user.id).present? end end - memoize :user_has_been_instructor? def user_has_been_admin?(user) return unless user @@ -570,7 +567,6 @@ class Course < ActiveRecord::Base self.admin_enrollments.active.find_by_user_id(user.id).present? end end - memoize :user_has_been_admin? def user_has_been_observer?(user) return unless user @@ -579,7 +575,6 @@ class Course < ActiveRecord::Base self.observer_enrollments.active.find_by_user_id(user.id).present? end end - memoize :user_has_been_observer? def user_has_been_student?(user) return unless user @@ -587,7 +582,6 @@ class Course < ActiveRecord::Base self.all_student_enrollments.find_by_user_id(user.id).present? end end - memoize :user_has_been_student? def user_has_no_enrollments?(user) return unless user @@ -595,7 +589,6 @@ class Course < ActiveRecord::Base enrollments.find_by_user_id(user.id).nil? end end - memoize :user_has_no_enrollments? # Public: Determine if a group weighting scheme should be applied. @@ -751,9 +744,8 @@ class Course < ActiveRecord::Base end def long_self_enrollment_code - Digest::MD5.hexdigest("#{uuid}_for_#{id}") + @long_self_enrollment_code ||= Digest::MD5.hexdigest("#{uuid}_for_#{id}") end - memoize :long_self_enrollment_code # still include the old longer format, since links may be out there def self_enrollment_codes @@ -1173,13 +1165,11 @@ class Course < ActiveRecord::Base def account_chain_ids account_chain.map(&:id) end - memoize :account_chain_ids def institution_name return self.root_account.name if self.root_account_id != Account.default.id return (self.account || self.root_account).name end - memoize :institution_name def account_users_for(user) return [] unless user @@ -1260,7 +1250,6 @@ class Course < ActiveRecord::Base end message end - memoize :grade_publishing_status_translation def grade_publishing_statuses found_statuses = [].to_set @@ -1750,7 +1739,6 @@ class Course < ActiveRecord::Base # has valid settings account.turnitin_settings end - memoize :turnitin_settings def turnitin_pledge self.account.closest_turnitin_pledge @@ -2290,7 +2278,6 @@ class Course < ActiveRecord::Base end end end - memoize :section_visibilities_for def visibility_limited_to_course_sections?(user, visibilities = section_visibilities_for(user)) visibilities.all?{|s| s[:limit_privileges_to_course_section] } @@ -2482,9 +2469,15 @@ class Course < ActiveRecord::Base end def tabs_available(user=nil, opts={}) + opts.reverse_merge!(:include_external => true) + cache_key = [user, opts].cache_key + @tabs_available ||= {} + @tabs_available[cache_key] ||= uncached_tabs_available(user, opts) + end + + def uncached_tabs_available(user, opts) # make sure t() is called before we switch to the slave, in case we update the user's selected locale in the process default_tabs = Course.default_tabs - opts.reverse_merge!(:include_external => true) Shackles.activate(:slave) do # We will by default show everything in default_tabs, unless the teacher has configured otherwise. @@ -2586,7 +2579,6 @@ class Course < ActiveRecord::Base tabs end end - memoize :tabs_available def allow_wiki_comments read_attribute(:allow_wiki_comments) diff --git a/app/models/delayed_notification.rb b/app/models/delayed_notification.rb index 8e7a9b44497..6b742ed1e93 100644 --- a/app/models/delayed_notification.rb +++ b/app/models/delayed_notification.rb @@ -59,6 +59,7 @@ class DelayedNotification < ActiveRecord::Base end def to_list + return @to_list if @to_list lookups = {} (recipient_keys || []).each do |key| pieces = key.split('_') @@ -73,10 +74,9 @@ class DelayedNotification < ActiveRecord::Base includes = [:user] if klass == CommunicationChannel res += klass.where(:id => ids).includes(includes).all rescue [] end - res.uniq + @to_list = res.uniq end - memoize :to_list - + scope :to_be_processed, lambda { |limit| where(:workflow_state => 'to_be_processed').limit(limit).order("delayed_notifications.created_at") } diff --git a/app/models/developer_key.rb b/app/models/developer_key.rb index 152b127ac42..a43b6c93d58 100644 --- a/app/models/developer_key.rb +++ b/app/models/developer_key.rb @@ -54,10 +54,7 @@ class DeveloperKey < ActiveRecord::Base @special_keys ||= {} if Rails.env.test? - # TODO: we have to do this because tests run in transactions. maybe it'd - # be good to create some sort of of memoize_if_safe method, that only - # memoizes when we're caching classes and not in test mode? I dunno. But - # this stinks. + # TODO: we have to do this because tests run in transactions return @special_keys[default_key_name] = DeveloperKey.find_or_create_by_name(default_key_name) end diff --git a/app/models/discussion_entry.rb b/app/models/discussion_entry.rb index 9282789eac5..7dd8332dcd7 100644 --- a/app/models/discussion_entry.rb +++ b/app/models/discussion_entry.rb @@ -228,7 +228,7 @@ class DiscussionEntry < ActiveRecord::Base end def update_topic_subscription - discussion_topic.user_ids_who_have_posted_and_admins(true) # pesky memoization + discussion_topic.user_ids_who_have_posted_and_admins unless discussion_topic.user_can_see_posts?(user) discussion_topic.unsubscribe(user) end diff --git a/app/models/discussion_topic.rb b/app/models/discussion_topic.rb index 246cd13b9b0..456322632f8 100644 --- a/app/models/discussion_topic.rb +++ b/app/models/discussion_topic.rb @@ -518,7 +518,6 @@ class DiscussionTopic < ActiveRecord::Base ids += self.context.admin_enrollments.active.pluck(:user_id) if self.context.respond_to?(:admin_enrollments) ids end - memoize :user_ids_who_have_posted_and_admins def user_can_see_posts?(user, session=nil) return false unless user diff --git a/app/models/folder.rb b/app/models/folder.rb index e9829e6220f..e77e0ffe8d9 100644 --- a/app/models/folder.rb +++ b/app/models/folder.rb @@ -163,40 +163,40 @@ class Folder < ActiveRecord::Base res += self.active_file_attachments unless opts[:exclude_files] res end - + def visible? # everything but private folders should be visible... for now... - (self.workflow_state == "visible") && (!self.parent_folder || self.parent_folder.visible?) + return @visible if defined?(@visible) + @visible = (self.workflow_state == "visible") && (!self.parent_folder || self.parent_folder.visible?) end - memoize :visible? - + def hidden? - self.workflow_state == 'hidden' || (self.parent_folder && self.parent_folder.hidden?) + return @hidden if defined?(@hidden) + @hidden = self.workflow_state == 'hidden' || (self.parent_folder && self.parent_folder.hidden?) end - memoize :hidden? - + def hidden hidden? end - + def hidden=(val) self.workflow_state = (val == true || val == '1' || val == 'true' ? 'hidden' : 'visible') end - + def just_hide self.workflow_state == 'hidden' end - + def protected? - (self.workflow_state == 'protected') || (self.parent_folder && self.parent_folder.protected?) + return @protected if defined?(@protected) + @protected = (self.workflow_state == 'protected') || (self.parent_folder && self.parent_folder.protected?) end - memoize :protected? - + def public? - self.workflow_state == 'public' || (self.parent_folder && self.parent_folder.public?) + return @public if defined?(@public) + @public = self.workflow_state == 'public' || (self.parent_folder && self.parent_folder.public?) end - memoize :public? - + def mime_class "folder" end @@ -351,17 +351,17 @@ class Folder < ActiveRecord::Base end def locked? - self.locked || - (self.lock_at && Time.now > self.lock_at) || - (self.unlock_at && Time.now < self.unlock_at) || - (self.parent_folder && self.parent_folder.locked?) + return @locked if defined?(@locked) + @locked = self.locked || + (self.lock_at && Time.now > self.lock_at) || + (self.unlock_at && Time.now < self.unlock_at) || + (self.parent_folder && self.parent_folder.locked?) end - memoize :locked? def currently_locked self.locked || (self.lock_at && Time.now > self.lock_at) || (self.unlock_at && Time.now < self.unlock_at) || self.workflow_state == 'hidden' end - + set_policy do given { |user, session| self.visible? && self.cached_context_grants_right?(user, session, :read) }#students.include?(user) } can :read diff --git a/app/models/submission.rb b/app/models/submission.rb index 2bee02db584..59b808b92c8 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -852,9 +852,8 @@ class Submission < ActiveRecord::Base end def commenting_instructors - comment_authors & context.instructors + @commenting_instructors ||= comment_authors & context.instructors end - memoize :commenting_instructors def participating_instructors commenting_instructors.present? ? commenting_instructors : context.participating_instructors.uniq diff --git a/app/models/user.rb b/app/models/user.rb index 69306432f11..a8cd18a285c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -956,9 +956,8 @@ class User < ActiveRecord::Base end def courses_with_grades - self.available_courses.with_each_shard.select{|c| c.grants_right?(self, nil, :participate_as_student)} + @courses_with_grades ||= self.available_courses.with_each_shard.select{|c| c.grants_right?(self, nil, :participate_as_student)} end - memoize :courses_with_grades def sis_pseudonym_for(context) root_account = context.root_account @@ -1346,12 +1345,6 @@ class User < ActiveRecord::Base opts = { :start_at => 1.week.ago, :limit => 10 }.merge(opts) Submission.recently_graded_assignments(id, opts[:start_at], opts[:limit]) end - memoize :assignments_recently_graded - - def assignments_recently_graded_total_count(opts={}) - assignments_recently_graded(opts.merge({:limit => nil})).size - end - memoize :assignments_recently_graded_total_count def preferences read_attribute(:preferences) || write_attribute(:preferences, {}) @@ -1410,73 +1403,71 @@ class User < ActiveRecord::Base end def assignments_needing_submitting(opts={}) - Shackles.activate(:slave) do - course_ids = if opts[:contexts] + course_ids = Shackles.activate(:slave) do + if opts[:contexts] (Array(opts[:contexts]).map(&:id) & current_student_enrollment_course_ids) else current_student_enrollment_course_ids end - - # allow explicitly passing a nil limit - limit = opts[:limit] - limit = 15 unless opts.key?(:limit) - due_after = opts[:due_after] || 4.weeks.ago - - result = Shard.partition_by_shard(course_ids) do |shard_course_ids| - Assignment.for_course(shard_course_ids). - active. - due_between_with_overrides(due_after,1.week.from_now). - not_ignored_by(self, 'submitting'). - expecting_submission. - need_submitting_info(id, limit). - not_locked - end - # outer limit, since there could be limit * n_shards results - result = result[0..(limit - 1)] if limit - result end - end - memoize :assignments_needing_submitting - # every couple years i try to remove this since it appears to be unused. - # turns out we do actually use it in a view via User#send w/ string - # interpolation ಠ_ಠ - def assignments_needing_submitting_total_count(opts={}) - assignments_needing_submitting(opts.merge(:limit => nil)).size + opts = {limit: 15}.merge(opts.slice(:due_after, :limit)) + + shard.activate do + Rails.cache.fetch([self, 'assignments_needing_submitting', course_ids, opts].cache_key, expires_in: 15.minutes) do + Shackles.activate(:slave) do + limit = opts[:limit] + due_after = opts[:due_after] || 4.weeks.ago + + result = Shard.partition_by_shard(course_ids) do |shard_course_ids| + Assignment.for_course(shard_course_ids). + active. + due_between_with_overrides(due_after,1.week.from_now). + not_ignored_by(self, 'submitting'). + expecting_submission. + need_submitting_info(id, limit). + not_locked + end + # outer limit, since there could be limit * n_shards results + result = result[0...limit] if limit + result + end + end + end end def assignments_needing_grading(opts={}) - Shackles.activate(:slave) do - course_ids = if opts[:contexts] + course_ids = Shackles.activate(:slave) do + if opts[:contexts] (Array(opts[:contexts]).map(&:id) & current_admin_enrollment_course_ids) else current_admin_enrollment_course_ids end - - # allow explicitly passing a nil limit - limit = opts[:limit] - limit = 15 unless opts.key?(:limit) - - result = Shard.partition_by_shard(course_ids) do |shard_course_ids| - as = Assignment.for_course(shard_course_ids).active. - expecting_submission. - not_ignored_by(self, 'grading'). - need_grading_info(limit) - Assignment.send :preload_associations, as, :context - as.reject{|a| a.needs_grading_count_for_user(self) == 0} - end - # outer limit, since there could be limit * n_shards results - result = result[0..(limit - 1)] if limit - result end - end - memoize :assignments_needing_grading - # see assignments_needing_submitting_total_count - def assignments_needing_grading_total_count(opts={}) - assignments_needing_grading(opts.merge(:limit => nil)).size + opts = {limit: 15}.merge(opts.slice(:limit)) + + shard.activate do + Rails.cache.fetch([self, 'assignments_needing_grading', course_ids, opts].cache_key, expires_in: 15.minutes) do + Shackles.activate(:slave) do + limit = opts[:limit] + + result = Shard.partition_by_shard(course_ids) do |shard_course_ids| + as = Assignment.for_course(shard_course_ids).active. + expecting_submission. + not_ignored_by(self, 'grading'). + need_grading_info(limit) + Assignment.send :preload_associations, as, :context + as.reject{|a| a.needs_grading_count_for_user(self) == 0} + end + # outer limit, since there could be limit * n_shards results + result = result[0...limit] if limit + result + end + end + end end def generate_access_verifier(ts) @@ -1607,59 +1598,63 @@ class User < ActiveRecord::Base end def courses_with_primary_enrollment(association = :current_and_invited_courses, enrollment_uuid = nil, options = {}) - res = self.shard.activate do - Rails.cache.fetch([self, 'courses_with_primary_enrollment', association, options].cache_key, :expires_in => 15.minutes) do + cache_key = [association, enrollment_uuid, options].cache_key + @courses_with_primary_enrollment ||= {} + @courses_with_primary_enrollment.fetch(cache_key) do + res = self.shard.activate do + Rails.cache.fetch([self, 'courses_with_primary_enrollment', association, options].cache_key, :expires_in => 15.minutes) do - # Set the actual association based on if its asking for favorite courses or not. - actual_association = association == :favorite_courses ? :current_and_invited_courses : association + # Set the actual association based on if its asking for favorite courses or not. + actual_association = association == :favorite_courses ? :current_and_invited_courses : association - send(actual_association).with_each_shard do |scope| + send(actual_association).with_each_shard do |scope| - # Limit favorite courses based on current shard. - if association == :favorite_courses - local_ids = self.favorite_context_ids("Course") - next if local_ids.length < 1 - scope = scope.where(:id => local_ids) + # Limit favorite courses based on current shard. + if association == :favorite_courses + local_ids = self.favorite_context_ids("Course") + next if local_ids.length < 1 + scope = scope.where(:id => local_ids) + end + + courses = scope.distinct_on(["courses.id"], + :select => "courses.*, enrollments.id AS primary_enrollment_id, enrollments.type AS primary_enrollment, #{Enrollment.type_rank_sql} AS primary_enrollment_rank, enrollments.workflow_state AS primary_enrollment_state", + :order => "courses.id, #{Enrollment.type_rank_sql}, #{Enrollment.state_rank_sql}") + + unless options[:include_completed_courses] + enrollments = Enrollment.where(:id => courses.map(&:primary_enrollment_id)).all + courses_hash = courses.index_by(&:id) + # prepopulate the reverse association + enrollments.each { |e| e.course = courses_hash[e.course_id] } + Canvas::Builders::EnrollmentDateBuilder.preload(enrollments) + date_restricted_ids = enrollments.select{ |e| e.completed? || e.inactive? }.map(&:id) + courses.reject! { |course| date_restricted_ids.include?(course.primary_enrollment_id.to_i) } + end + + courses end + end.dup + end - courses = scope.distinct_on(["courses.id"], - :select => "courses.*, enrollments.id AS primary_enrollment_id, enrollments.type AS primary_enrollment, #{Enrollment.type_rank_sql} AS primary_enrollment_rank, enrollments.workflow_state AS primary_enrollment_state", - :order => "courses.id, #{Enrollment.type_rank_sql}, #{Enrollment.state_rank_sql}") - - unless options[:include_completed_courses] - enrollments = Enrollment.where(:id => courses.map(&:primary_enrollment_id)).all - courses_hash = courses.index_by(&:id) - # prepopulate the reverse association - enrollments.each { |e| e.course = courses_hash[e.course_id] } - Canvas::Builders::EnrollmentDateBuilder.preload(enrollments) - date_restricted_ids = enrollments.select{ |e| e.completed? || e.inactive? }.map(&:id) - courses.reject! { |course| date_restricted_ids.include?(course.primary_enrollment_id.to_i) } - end - - courses + if association == :current_and_invited_courses + if enrollment_uuid && pending_course = Course. + select("courses.*, enrollments.type AS primary_enrollment, #{Enrollment.type_rank_sql} AS primary_enrollment_rank, enrollments.workflow_state AS primary_enrollment_state"). + joins(:enrollments). + where(:enrollments => { :uuid => enrollment_uuid, :workflow_state => 'invited' }).first + res << pending_course + res.uniq! + end + pending_enrollments = temporary_invitations + unless pending_enrollments.empty? + Enrollment.send(:preload_associations, pending_enrollments, :course) + res.concat(pending_enrollments.map { |e| c = e.course; c.write_attribute(:primary_enrollment, e.type); c.write_attribute(:primary_enrollment_rank, e.rank_sortable.to_s); c.write_attribute(:primary_enrollment_state, e.workflow_state); c.write_attribute(:invitation, e.uuid); c }) + res.uniq! end - end.dup - end - - if association == :current_and_invited_courses - if enrollment_uuid && pending_course = Course. - select("courses.*, enrollments.type AS primary_enrollment, #{Enrollment.type_rank_sql} AS primary_enrollment_rank, enrollments.workflow_state AS primary_enrollment_state"). - joins(:enrollments). - where(:enrollments => { :uuid => enrollment_uuid, :workflow_state => 'invited' }).first - res << pending_course - res.uniq! end - pending_enrollments = temporary_invitations - unless pending_enrollments.empty? - Enrollment.send(:preload_associations, pending_enrollments, :course) - res.concat(pending_enrollments.map { |e| c = e.course; c.write_attribute(:primary_enrollment, e.type); c.write_attribute(:primary_enrollment_rank, e.rank_sortable.to_s); c.write_attribute(:primary_enrollment_state, e.workflow_state); c.write_attribute(:invitation, e.uuid); c }) - res.uniq! - end - end - res.sort_by{ |c| [c.primary_enrollment_rank, Canvas::ICU.collation_key(c.name)] } + @courses_with_primary_enrollment[cache_key] = + res.sort_by{ |c| [c.primary_enrollment_rank, Canvas::ICU.collation_key(c.name)] } + end end - memoize :courses_with_primary_enrollment def cached_active_emails self.shard.activate do @@ -1673,7 +1668,6 @@ class User < ActiveRecord::Base cached_active_emails.map { |email| Enrollment.cached_temporary_invitations(email).dup.reject { |e| e.user_id == self.id } }.flatten end - # activesupport/lib/active_support/memoizable.rb from rails and # http://github.com/seamusabshere/cacheable/blob/master/lib/cacheable.rb from the cacheable gem # to get a head start @@ -1690,7 +1684,6 @@ class User < ActiveRecord::Base end end + temporary_invitations end - memoize :cached_current_enrollments def cached_not_ended_enrollments self.shard.activate do @@ -1736,45 +1729,52 @@ class User < ActiveRecord::Base end def submissions_for_context_codes(context_codes, opts={}) - return [] if (!context_codes || context_codes.empty?) - opts[:start_at] ||= 2.weeks.ago - opts[:limit] ||= 20 + return [] unless context_codes.present? - Shackles.activate(:slave) do - submissions = [] - submissions += self.submissions.after(opts[:start_at]).for_context_codes(context_codes). - where("submissions.score IS NOT NULL AND assignments.workflow_state<>? AND assignments.muted=?", 'deleted', false). - order('submissions.created_at DESC'). - limit(opts[:limit]).all + opts = {limit: 20}.merge(opts.slice(:start_at, :limit)) + shard.activate do + Rails.cache.fetch([self, 'submissions_for_context_codes', context_codes, opts].cache_key, expires_in: 15.minutes) do + opts[:start_at] ||= 2.weeks.ago - # THIS IS SLOW, it takes ~230ms for mike - submissions += Submission.for_context_codes(context_codes). - select(["submissions.*, last_updated_at_from_db"]). - joins(self.class.send(:sanitize_sql_array, [<<-SQL, opts[:start_at], self.id, self.id])). - INNER JOIN ( - SELECT MAX(submission_comments.created_at) AS last_updated_at_from_db, submission_id - FROM submission_comments, submission_comment_participants - WHERE submission_comments.id = submission_comment_id - AND (submission_comments.created_at > ?) - AND (submission_comment_participants.user_id = ?) - AND (submission_comments.author_id <> ?) - GROUP BY submission_id - ) AS relevant_submission_comments ON submissions.id = submission_id - INNER JOIN assignments ON assignments.id = submissions.assignment_id AND assignments.workflow_state <> 'deleted' - SQL - where(assignments: {muted: false}). - order('last_updated_at_from_db DESC'). - limit(opts[:limit]).all + Shackles.activate(:slave) do + submissions = [] + submissions += self.submissions.after(opts[:start_at]).for_context_codes(context_codes). + where("submissions.score IS NOT NULL AND assignments.workflow_state<>? AND assignments.muted=?", 'deleted', false). + order('submissions.created_at DESC'). + limit(opts[:limit]).all - submissions = submissions.sort_by{|t| (t.last_updated_at_from_db.to_datetime.in_time_zone rescue nil) || t.created_at}.reverse - submissions = submissions.uniq - submissions.first(opts[:limit]) + # THIS IS SLOW, it takes ~230ms for mike + submissions += Submission.for_context_codes(context_codes). + select(["submissions.*, last_updated_at_from_db"]). + joins(self.class.send(:sanitize_sql_array, [<<-SQL, opts[:start_at], self.id, self.id])). + INNER JOIN ( + SELECT MAX(submission_comments.created_at) AS last_updated_at_from_db, submission_id + FROM submission_comments, submission_comment_participants + WHERE submission_comments.id = submission_comment_id + AND (submission_comments.created_at > ?) + AND (submission_comment_participants.user_id = ?) + AND (submission_comments.author_id <> ?) + GROUP BY submission_id + ) AS relevant_submission_comments ON submissions.id = submission_id + INNER JOIN assignments ON assignments.id = submissions.assignment_id AND assignments.workflow_state <> 'deleted' + SQL + where(assignments: {muted: false}). + order('last_updated_at_from_db DESC'). + limit(opts[:limit]).all - Submission.send(:preload_associations, submissions, [:assignment, :user, :submission_comments]) - submissions + submissions = submissions.sort_by{|t| (t.last_updated_at_from_db.to_datetime.in_time_zone rescue nil) || t.created_at}.reverse + submissions = submissions.uniq + submissions.first(opts[:limit]) + + Submission.send(:preload_associations, submissions, [:assignment, :user, :submission_comments]) + submissions + end + end end end - memoize :submissions_for_context_codes + + def uncached_submissions_for_context_codes(context_codes, opts) + end # This is only feedback for student contexts (unless specific contexts are passed in) def recent_feedback(opts={}) @@ -1786,7 +1786,6 @@ class User < ActiveRecord::Base end submissions_for_context_codes(context_codes, opts) end - memoize :recent_feedback def visible_stream_item_instances(opts={}) instances = stream_item_instances.where(:hidden => false).order('stream_item_instances.id desc') @@ -1915,7 +1914,6 @@ class User < ActiveRecord::Base # TODO: All the event methods use this and it's really slow. Array(contexts || cached_contexts).map(&:asset_string) end - memoize :setup_context_lookups def setup_context_association_lookups(column, contexts=nil, opts = {}) contexts = Array(contexts || cached_contexts) @@ -1959,6 +1957,7 @@ class User < ActiveRecord::Base # context codes of things that might have a schedulable appointment for the # given user, i.e. courses and sections def appointment_context_codes + return @appointment_context_codes if @appointment_context_codes ret = {:primary => [], :secondary => []} cached_current_enrollments.each do |e| next unless e.student? && e.active? @@ -1966,11 +1965,11 @@ class User < ActiveRecord::Base ret[:secondary] << "course_section_#{e.course_section_id}" end ret[:secondary].concat groups.map{ |g| "group_category_#{g.group_category_id}" } - ret + @appointment_context_codes = ret end - memoize :appointment_context_codes def manageable_appointment_context_codes + return @manageable_appointment_context_codes if @manageable_appointment_context_codes ret = {:full => [], :limited => [], :secondary => []} cached_current_enrollments.each do |e| next unless e.course.grants_right?(self, nil, :manage_calendar) @@ -1981,9 +1980,8 @@ class User < ActiveRecord::Base ret[:full] << "course_#{e.course_id}" end end - ret + @manageable_appointment_context_codes = ret end - memoize :manageable_appointment_context_codes # Public: Return an array of context codes this user belongs to. # @@ -2004,7 +2002,6 @@ class User < ActiveRecord::Base end end end - memoize :conversation_context_codes def self.preload_conversation_context_codes(users) users = users.reject { |u| u.instance_variable_get(:@conversation_context_codes) } @@ -2127,21 +2124,17 @@ class User < ActiveRecord::Base TAB_HOME = 4 def highest_role - return 'admin' unless self.all_accounts.empty? - return 'teacher' if self.cached_current_enrollments.any?(&:admin?) - return 'student' if self.cached_current_enrollments.any?(&:student?) - return 'user' + roles.last end - memoize :highest_role def roles + return @roles if @roles res = ['user'] res << 'student' if self.cached_current_enrollments.any?(&:student?) res << 'teacher' if self.cached_current_enrollments.any?(&:admin?) res << 'admin' unless self.all_accounts.empty? - res + @roles = res end - memoize :roles def eportfolios_enabled? accounts = associated_root_accounts.reject(&:site_admin?) @@ -2561,23 +2554,21 @@ class User < ActiveRecord::Base end def all_accounts - self.accounts.with_each_shard + @all_accounts ||= self.accounts.with_each_shard end - memoize :all_accounts def all_paginatable_accounts BookmarkedCollection.with_each_shard(Account::Bookmarker, self.accounts) end def all_pseudonyms - self.pseudonyms.with_each_shard + @all_pseudonyms ||= self.pseudonyms.with_each_shard end - memoize :all_pseudonyms - def all_active_pseudonyms - self.pseudonyms.with_each_shard { |scope| scope.active } + def all_active_pseudonyms(reload=false) + @all_active_pseudonyms = nil if reload + @all_active_pseudonyms ||= self.pseudonyms.with_each_shard { |scope| scope.active } end - memoize :all_active_pseudonyms def prefers_gradebook2? preferences[:use_gradebook2] != false diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 63304e6d006..5c27914f554 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -74,26 +74,26 @@ <%- max_to_show = 8 assignment_sets = { - :needing_submitting => t('menu.assignments_needing_submitting', "To Turn In"), - :needing_grading => t('menu.assignments_needing_grading', "To Grade"), - :recently_graded => t('menu.assignments_recently_graded', "Recently Graded") + :assignments_needing_submitting => t('menu.assignments_needing_submitting', "To Turn In"), + :assignments_needing_grading => t('menu.assignments_needing_grading', "To Grade"), + :assignments_recently_graded => t('menu.assignments_recently_graded', "Recently Graded") } total_shown = 0 - if assignment_sets.any?{ |k, v| @current_user.send("assignments_#{k}").length > 0 } + if assignment_sets.any?{ |k, v| @current_user.send(k).length > 0 } -%> <%= t('menu.assignments', 'Assignments') %>