canvas-lms/config/initializers/active_record.rb

278 lines
9.4 KiB
Ruby

class ActiveRecord::Base
extend ActiveSupport::Memoizable # used for a lot of the reporting queries
class ProtectedAttributeAssigned < Exception; end
def log_protected_attribute_removal_with_raise(*attributes)
if Canvas.protected_attribute_error == :raise
raise ProtectedAttributeAssigned, "Can't mass-assign these protected attributes for class #{self.class.name}: #{attributes.join(', ')}"
else
log_protected_attribute_removal_without_raise(*attributes)
end
end
alias_method_chain :log_protected_attribute_removal, :raise
def feed_code
id = self.uuid rescue self.id
"#{self.class.base_ar_class.name.underscore}_#{id.to_s}"
end
def self.maximum_text_length
@maximum_text_length ||= 64.kilobytes-1
end
def self.maximum_long_text_length
@maximum_text_length ||= 500.kilobytes-1
end
def self.find_by_asset_string(string, asset_types)
code = string.split("_")
id = code.pop
code.join("_").classify.constantize.find(id) rescue nil
end
def self.initialize_by_asset_string(string, asset_types)
code = string.split("_")
id = code.pop
res = code.join("_").classify.constantize rescue nil
res.id = id if res
res
end
def self.find_cached(key, &block)
attrs = Rails.cache.read(key)
from_cache = true
if !attrs || attrs.empty? || attrs.is_a?(String) || attrs[:assigned_cache_key] != key
obj = block.call rescue nil
attrs = obj && obj.is_a?(self) ? obj.attributes : nil
attrs[:assigned_cache_key] = key if attrs
Rails.cache.write(key, attrs) if attrs
from_cache = false
end
return nil if !attrs || attrs.empty?
obj = self.new
attrs = attrs.dup if attrs.frozen?
attrs.delete(:assigned_cache_key)
obj.instance_variable_set("@attributes", attrs)
obj.instance_variable_set("@new_record", false)
obj
end
def asset_string
@asset_string ||= "#{self.class.base_ar_class.name.underscore}_#{id.to_s}"
end
def export_columns(format = nil)
self.class.content_columns.map(&:name) - ['created_at', 'updated_at']
end
def to_row(format = nil)
export_columns(format).map { |c| self.send(c) }
end
def is_a_context?
false
end
def self.clear_cached_contexts
@@cached_contexts = {}
@@cached_permissions = {}
end
def cached_context_grants_right?(user, session, permission, context_key=nil)
@@cached_contexts = nil if ENV['RAILS_ENV'] == "test"
@@cached_contexts ||= {}
context_key ||= "#{self.context_type}_#{self.context_id}"
@@cached_contexts[context_key] ||= self.context rescue nil
@@cached_contexts[context_key] ||= self.course rescue nil
@@cached_permissions ||= {}
key = [context_key, (user ? user.id : nil)].join
@@cached_permissions[key] = nil if ENV['RAILS_ENV'] == "test"
@@cached_permissions[key] = nil if session && session[:session_affects_permissions]
@@cached_permissions[key] ||= @@cached_contexts[context_key].grants_rights?(user, session, nil)
@@cached_permissions[key][permission]
end
def cached_course_grants_right?(user, session, permission)
cached_context_grants_right?(user, session, permission, "Course_#{self.course_id}")
end
def cached_context_short_name
if self.respond_to?(:context)
code = self.respond_to?(:context_code) ? self.context_code : self.context.asset_string
@cached_context_name ||= Rails.cache.fetch(['short_name_lookup', code].cache_key) do
self.context.short_name rescue ""
end
else
raise "Can only call cached_context_short_name on items with a context"
end
end
def self.skip_touch_context(skip=true)
@@skip_touch_context = skip
end
def save_without_touching_context
@skip_touch_context = true
self.save
@skip_touch_context = false
end
def touch_context
return if (@@skip_touch_context ||= false || @skip_touch_context ||= false)
if self.respond_to?(:context_type) && self.respond_to?(:context_id) && self.context_type && self.context_id
conn = ActiveRecord::Base.connection
conn.execute("UPDATE #{self.context_type.underscore.pluralize} SET updated_at=#{conn.quote(Time.now.utc.to_s(:db))} WHERE id=#{self.context_id}") rescue nil
end
end
def touch_user
if self.respond_to?(:user_id) && self.user_id
conn = ActiveRecord::Base.connection
conn.execute("UPDATE users SET updated_at=#{conn.quote(Time.now.utc.to_s(:db))} WHERE id=#{self.user_id}") rescue nil
User.invalidate_cache(self.user_id)
end
true
rescue
false
end
def context_url_prefix
"#{self.context_type.downcase.pluralize}/#{self.context_id}"
end
def send_later_if_production(*args)
if ENV['RAILS_ENV'] == 'production'
send_later(*args)
else
send(*args)
end
end
def self.send_later_if_production(*args)
if ENV['RAILS_ENV'] == 'production'
send_later(*args)
else
send(*args)
end
end
# Example:
# obj.to_json(:permissions => {:user => u, :policies => [:read, :write, :update]})
def as_json(options = nil)
options ||= {}
self.set_serialization_options rescue nil
options[:except] = [options[:except]]
options[:methods] = [options[:methods]]
options[:except] = (options[:except] + ([self.class.serialization_excludes] rescue []) + [@serialization_excludes]).flatten.compact
options[:methods] = (options[:methods] + ([self.class.serialization_methods] rescue []) + [@serialization_methods]).flatten.compact
options.delete :except if options[:except].empty?
options.delete :methods if options[:methods].empty?
# We include a root in all the association json objects (if it's a
# collection), which is different than the rails behavior of just including
# the root in the base json object. Hence the hackies.
#
# We are in the process of migrating away from including the root in all our
# json serializations at all. Once that's done, we can remove this and the
# monkey patch to Serialzer, below.
unless options.key?(:include_root)
options[:include_root] = ActiveRecord::Base.include_root_in_json
end
hash = Serializer.new(self, options).serializable_record
if options[:permissions]
permissions_hash = self.grants_rights?(options[:permissions][:user], options[:permissions][:session], *options[:permissions][:policies])
if options[:include_root]
hash[self.class.base_ar_class.model_name.element]["permissions"] = permissions_hash
else
hash["permissions"] = permissions_hash
end
end
self.revert_from_serialization_options rescue nil
hash
end
def class_name
self.class.to_s
end
def self.execute_with_sanitize(array)
self.connection.execute(__send__(:sanitize_sql_array, array))
end
def self.base_ar_class
class_of_active_record_descendant(self)
end
end
class ActiveRecord::Serialization::Serializer
def serializable_record
hash = {}.tap do |serializable_record|
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
add_includes do |association, records, opts|
if records.is_a?(Enumerable)
serializable_record[association] = records.compact.collect { |r| self.class.new(r, opts).serializable_record }
else
# don't include_root on non-plural associations
opts = opts.merge(:include_root => false)
serializable_record[association] = self.class.new(records, opts).serializable_record
end
end
end
hash = { @record.class.base_ar_class.model_name.element => hash } if options[:include_root]
hash
end
end
class ActiveRecord::Errors
def to_json
{:errors => @errors}.to_json
end
end
# We need to have 64-bit ids and foreign keys.
if defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
ActiveRecord::ConnectionAdapters::MysqlAdapter::NATIVE_DATABASE_TYPES[:primary_key] = "bigint DEFAULT NULL auto_increment PRIMARY KEY".freeze
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
def add_column_with_foreign_key_check(table, name, type, options = {})
Canvas.active_record_foreign_key_check(name, type, options)
add_column_without_foreign_key_check(table, name, type, options)
end
alias_method_chain :add_column, :foreign_key_check
end
end
if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:primary_key] = "bigserial primary key".freeze
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
def add_column_with_foreign_key_check(table, name, type, options = {})
Canvas.active_record_foreign_key_check(name, type, options)
add_column_without_foreign_key_check(table, name, type, options)
end
alias_method_chain :add_column, :foreign_key_check
end
end
ActiveRecord::ConnectionAdapters::SchemaStatements.class_eval do
def add_column_with_foreign_key_check(table, name, type, options = {})
Canvas.active_record_foreign_key_check(name, type, options)
add_column_without_foreign_key_check(table, name, type, options)
end
alias_method_chain :add_column, :foreign_key_check
end
ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do
def column_with_foreign_key_check(name, type, options = {})
Canvas.active_record_foreign_key_check(name, type, options)
column_without_foreign_key_check(name, type, options)
end
alias_method_chain :column, :foreign_key_check
end