2020-10-27 00:46:40 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-03-04 03:55:14 +08:00
|
|
|
#
|
2017-04-28 10:30:08 +08:00
|
|
|
# Copyright (C) 2014 - present Instructure, Inc.
|
2014-03-04 03:55:14 +08:00
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
#
|
|
|
|
|
|
|
|
class CustomData < ActiveRecord::Base
|
|
|
|
class WriteConflict < Exception
|
|
|
|
attr_accessor :conflict_scope, :type_at_conflict, :value_at_conflict
|
|
|
|
|
|
|
|
def initialize(opts = {})
|
|
|
|
opts.each do |k,v|
|
|
|
|
instance_variable_set("@#{k}", v)
|
|
|
|
end
|
|
|
|
super 'write conflict for custom_data hash'
|
|
|
|
end
|
2017-03-08 13:33:13 +08:00
|
|
|
|
|
|
|
def as_json
|
|
|
|
{
|
|
|
|
conflict_scope: conflict_scope,
|
|
|
|
type_at_conflict: type_at_conflict,
|
|
|
|
value_at_conflict: value_at_conflict
|
|
|
|
}
|
|
|
|
end
|
2014-03-04 03:55:14 +08:00
|
|
|
end
|
|
|
|
|
2014-07-24 01:14:22 +08:00
|
|
|
self.table_name = 'custom_data'
|
2014-03-04 03:55:14 +08:00
|
|
|
|
|
|
|
belongs_to :user
|
|
|
|
|
|
|
|
serialize :data, Hash
|
|
|
|
|
|
|
|
validates_presence_of :user, :namespace
|
|
|
|
|
|
|
|
def get_data(scope)
|
|
|
|
hash_data_from_scope(data_frd, "d/#{scope}")
|
|
|
|
end
|
|
|
|
|
2019-08-09 04:16:29 +08:00
|
|
|
def lock_and_save
|
|
|
|
transaction do
|
|
|
|
self.lock!
|
|
|
|
yield
|
|
|
|
self.destroyed? || save
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-03-04 03:55:14 +08:00
|
|
|
def set_data(scope, val)
|
|
|
|
set_hash_data_from_scope(data_frd, "d/#{scope}", val)
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_data(scope)
|
|
|
|
delete_hash_data_from_scope(data_frd, "d/#{scope}")
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def hash_data_from_scope(hash, scope)
|
|
|
|
keys = scope.split('/')
|
|
|
|
keys.inject(hash) do |hash, k|
|
|
|
|
raise ArgumentError, 'invalid scope for hash' unless hash.is_a? Hash
|
|
|
|
hash[k]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def set_hash_data_from_scope(hash, scope, data)
|
|
|
|
keys = scope.split('/')
|
|
|
|
last = keys.pop
|
|
|
|
|
|
|
|
traverse = ->(hsh, key_idx) do
|
|
|
|
return hsh if key_idx == keys.length
|
|
|
|
k = keys[key_idx]
|
|
|
|
h = hsh[k]
|
|
|
|
if h.nil?
|
|
|
|
hsh[k] = {}
|
|
|
|
elsif !h.is_a? Hash
|
|
|
|
raise WriteConflict.new({
|
|
|
|
conflict_scope: keys.slice(1..key_idx).join('/'),
|
|
|
|
type_at_conflict: h.class,
|
|
|
|
value_at_conflict: h
|
|
|
|
})
|
|
|
|
end
|
|
|
|
traverse.call(hsh[k], key_idx+1)
|
|
|
|
end
|
|
|
|
|
|
|
|
h = traverse.call(hash, 0)
|
|
|
|
overwrite = !h[last].nil?
|
|
|
|
h[last] = data
|
|
|
|
overwrite
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_hash_data_from_scope(hash, scope)
|
|
|
|
keys = scope.split('/')
|
|
|
|
del_frd = ->(hash) do
|
|
|
|
k = keys.shift
|
|
|
|
if keys.empty?
|
|
|
|
raise ArgumentError, 'invalid scope for hash' unless hash.has_key? k
|
|
|
|
hash.delete k
|
|
|
|
else
|
|
|
|
h = hash[k]
|
|
|
|
raise ArgumentError, 'invalid scope for hash' if h.nil?
|
|
|
|
ret = del_frd.call(h)
|
|
|
|
hash.delete k if h.empty?
|
|
|
|
ret
|
|
|
|
end
|
|
|
|
end
|
|
|
|
ret = del_frd.call(hash)
|
|
|
|
self.destroy if hash.empty?
|
|
|
|
ret
|
|
|
|
end
|
|
|
|
|
|
|
|
def data_frd
|
2015-12-29 03:01:30 +08:00
|
|
|
read_or_initialize_attribute(:data, {})
|
2014-03-04 03:55:14 +08:00
|
|
|
end
|
|
|
|
end
|