# # Copyright (C) 2011 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 . # module StickySisFields module InstanceMethods # this method is set as a before_update callback def set_sis_stickiness self.class.sis_stickiness_options ||= {} currently_stuck_sis_fields = if self.class.sis_stickiness_options[:clear_sis_stickiness] [].to_set else calculate_currently_stuck_sis_fields end if load_stuck_sis_fields_cache != currently_stuck_sis_fields write_attribute(:stuck_sis_fields, currently_stuck_sis_fields.map(&:to_s).sort.join(',')) end @stuck_sis_fields_cache = currently_stuck_sis_fields @sis_fields_to_stick = [].to_set @sis_fields_to_unstick = [].to_set end # this method is what you want to use to determine which fields are currently stuck def stuck_sis_fields self.class.sis_stickiness_options ||= {} if self.class.sis_stickiness_options[:override_sis_stickiness] return [].to_set else return calculate_currently_stuck_sis_fields end end def stuck_sis_fields=(fields) fields = [fields] if (fields.is_a? String) clear_sis_stickiness(*(stuck_sis_fields.to_a)) add_sis_stickiness(*(fields.map(&:to_sym).to_set)) end # clear stickiness on a set of fields def clear_sis_stickiness(*fields) @sis_fields_to_stick ||= [].to_set @sis_fields_to_unstick ||= [].to_set fields.map(&:to_sym).each do |field| @sis_fields_to_stick.delete field @sis_fields_to_unstick.add field end end # make some fields sticky def add_sis_stickiness(*fields) @sis_fields_to_stick ||= [].to_set @sis_fields_to_unstick ||= [].to_set fields.map(&:to_sym).each do |field| @sis_fields_to_stick.add field @sis_fields_to_unstick.delete field end end private def load_stuck_sis_fields_cache @stuck_sis_fields_cache ||= (read_attribute(:stuck_sis_fields) || '').split(',').map(&:to_sym).to_set end def calculate_currently_stuck_sis_fields @sis_fields_to_stick ||= [].to_set @sis_fields_to_unstick ||= [].to_set changed_sis_fields = self.class.sticky_sis_fields & (self.changed.map(&:to_sym).to_set | @sis_fields_to_stick) return (load_stuck_sis_fields_cache | changed_sis_fields) - @sis_fields_to_unstick end def reload_with_sis_stickiness(*a) @stuck_sis_fields_cache = @sis_fields_to_stick = @sis_fields_to_unstick = nil reload_without_sis_stickiness(*a) end end module ClassMethods # specify which fields are able to be stuck def are_sis_sticky(*fields) self.sticky_sis_fields = fields.map(&:to_sym).to_set end # takes a block and runs it with the following options: # override_sis_stickiness: default false, # if true, all code inside the block will be run as if the class # mixing in this module has no stuck sis fields # add_sis_stickiness: default false, # unless add_sis_stickiness (or clear_sis_stickiness) is true, the # set_sis_stickiness callback is skipped, so no sis stickiness is # modified. if true, the set_sis_stickiness is enabled like normal, # such that everything is processed like non-sis. it doesn't really # make tons of sense to use this feature without # override_sis_stickiness. # clear_sis_stickiness: default false, # if true, the set_sis_stickiness callback is enabled and configured # to write out an empty stickiness list on every save. def process_as_sis(opts={}) self.sis_stickiness_options ||= {} old_options = self.sis_stickiness_options.clone [:override_sis_stickiness, :clear_sis_stickiness].each do |option| self.sis_stickiness_options[option] = opts[option] end begin if opts[:add_sis_stickiness] || opts[:clear_sis_stickiness] yield else self.suspend_callbacks(:set_sis_stickiness) do yield end end ensure self.sis_stickiness_options = old_options end end end def self.included(klass) if klass < ActiveRecord::Base klass.send :extend, ClassMethods klass.send :include, InstanceMethods klass.cattr_accessor :sticky_sis_fields klass.cattr_accessor :sis_stickiness_options klass.before_update :set_sis_stickiness klass.alias_method_chain :reload, :sis_stickiness end end end