539 lines
13 KiB
Ruby
539 lines
13 KiB
Ruby
|
class ObjectManager::Attribute < ApplicationModel
|
||
|
self.table_name = 'object_manager_attributes'
|
||
|
belongs_to :object_lookup, class_name: 'ObjectLookup'
|
||
|
validates :name, presence: true
|
||
|
store :screens
|
||
|
store :data_option
|
||
|
|
||
|
notify_clients_support
|
||
|
|
||
|
=begin
|
||
|
|
||
|
list of all attributes
|
||
|
|
||
|
result = ObjectManager::Attribute.list_full
|
||
|
|
||
|
result = [
|
||
|
{
|
||
|
name: 'some name',
|
||
|
display: '...',
|
||
|
}.
|
||
|
],
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.list_full
|
||
|
result = ObjectManager::Attribute.all
|
||
|
attributes = []
|
||
|
assets = {}
|
||
|
result.each {|item|
|
||
|
attribute = item.attributes
|
||
|
attribute[:object] = ObjectLookup.by_id(item.object_lookup_id)
|
||
|
attribute.delete('object_lookup_id')
|
||
|
attributes.push attribute
|
||
|
}
|
||
|
attributes
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
add a new attribute entry for an object
|
||
|
|
||
|
ObjectManager::Attribute.add(
|
||
|
object: 'Ticket',
|
||
|
name: 'group_id',
|
||
|
display: 'Group',
|
||
|
data_type: 'select',
|
||
|
data_option: {
|
||
|
relation: 'Group',
|
||
|
relation_condition: { access: 'rw' },
|
||
|
multiple: false,
|
||
|
null: true,
|
||
|
translate: false,
|
||
|
},
|
||
|
active: true,
|
||
|
screens: {
|
||
|
create: {
|
||
|
'-all-' => {
|
||
|
required: true,
|
||
|
},
|
||
|
},
|
||
|
edit: {
|
||
|
Agent : {
|
||
|
required: true,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
position: 20,
|
||
|
created_by_id: 1,
|
||
|
updated_by_id: 1,
|
||
|
created_at: '2014-06-04 10:00:00',
|
||
|
updated_at: '2014-06-04 10:00:00',
|
||
|
|
||
|
force: true
|
||
|
editable: false,
|
||
|
to_migrate: false,
|
||
|
to_create: false,
|
||
|
to_delete: false,
|
||
|
)
|
||
|
|
||
|
preserved name are
|
||
|
|
||
|
/(_id|_ids)$/
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.add(data)
|
||
|
|
||
|
force = data[:force]
|
||
|
data.delete(:force)
|
||
|
|
||
|
# lookups
|
||
|
if data[:object]
|
||
|
data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
|
||
|
end
|
||
|
data.delete(:object)
|
||
|
|
||
|
data[:name].downcase!
|
||
|
|
||
|
# check new entry - is needed
|
||
|
record = ObjectManager::Attribute.find_by(
|
||
|
object_lookup_id: data[:object_lookup_id],
|
||
|
name: data[:name],
|
||
|
)
|
||
|
if record
|
||
|
|
||
|
# do not allow to overwrite certain attributes
|
||
|
if !force
|
||
|
data.delete(:editable)
|
||
|
data.delete(:to_create)
|
||
|
data.delete(:to_migrate)
|
||
|
data.delete(:to_delete)
|
||
|
end
|
||
|
# update attributes
|
||
|
data.each {|key, value|
|
||
|
record[key.to_sym] = value
|
||
|
}
|
||
|
|
||
|
# check editable & name
|
||
|
if !force
|
||
|
record.check_editable
|
||
|
record.check_name
|
||
|
end
|
||
|
record.check_datatype
|
||
|
record.save!
|
||
|
return record
|
||
|
end
|
||
|
|
||
|
# do not allow to overwrite certain attributes
|
||
|
if !force
|
||
|
data[:editable] = true
|
||
|
data[:to_create] = true
|
||
|
data[:to_migrate] = true
|
||
|
data[:to_delete] = false
|
||
|
end
|
||
|
|
||
|
record = ObjectManager::Attribute.new(data)
|
||
|
|
||
|
# check editable & name
|
||
|
if !force
|
||
|
record.check_editable
|
||
|
record.check_name
|
||
|
end
|
||
|
record.check_datatype
|
||
|
record.save!
|
||
|
record
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
remove attribute entry for an object
|
||
|
|
||
|
ObjectManager::Attribute.remove(
|
||
|
object: 'Ticket',
|
||
|
name: 'group_id',
|
||
|
)
|
||
|
|
||
|
use "force: true" to delete also not editable fields
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.remove(data)
|
||
|
|
||
|
# lookups
|
||
|
if data[:object]
|
||
|
data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
|
||
|
end
|
||
|
|
||
|
# check newest entry - is needed
|
||
|
record = ObjectManager::Attribute.find_by(
|
||
|
object_lookup_id: data[:object_lookup_id],
|
||
|
name: data[:name],
|
||
|
)
|
||
|
if !record
|
||
|
raise "ERROR: No such field #{data[:object]}.#{data[:name]}"
|
||
|
end
|
||
|
|
||
|
if !data[:force] && !record.editable
|
||
|
raise "ERROR: #{data[:object]}.#{data[:name]} can't be removed!"
|
||
|
end
|
||
|
|
||
|
# if record is to create, just destroy it
|
||
|
if record.to_create
|
||
|
record.destroy
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
record.to_delete = true
|
||
|
record.save
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
get the attribute model based on object and name
|
||
|
|
||
|
attribute = ObjectManager::Attribute.get(
|
||
|
object: 'Ticket',
|
||
|
name: 'group_id',
|
||
|
)
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.get(data)
|
||
|
|
||
|
# lookups
|
||
|
if data[:object]
|
||
|
data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
|
||
|
end
|
||
|
|
||
|
data[:name].downcase!
|
||
|
|
||
|
ObjectManager::Attribute.find_by(
|
||
|
object_lookup_id: data[:object_lookup_id],
|
||
|
name: data[:name],
|
||
|
)
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
get user based list of used object attributes
|
||
|
|
||
|
attribute_list = ObjectManager::Attribute.by_object('Ticket', user)
|
||
|
|
||
|
returns:
|
||
|
|
||
|
[
|
||
|
{ name: 'api_key', display: 'API KEY', tag: 'input', null: true, edit: true, maxlength: 32 },
|
||
|
{ name: 'api_ip_regexp', display: 'API IP RegExp', tag: 'input', null: true, edit: true },
|
||
|
{ name: 'api_ip_max', display: 'API IP Max', tag: 'input', null: true, edit: true },
|
||
|
]
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.by_object(object, user)
|
||
|
|
||
|
# lookups
|
||
|
if object
|
||
|
object_lookup_id = ObjectLookup.by_name(object)
|
||
|
end
|
||
|
|
||
|
# get attributes in right order
|
||
|
result = ObjectManager::Attribute.where(
|
||
|
object_lookup_id: object_lookup_id,
|
||
|
active: true,
|
||
|
to_create: false,
|
||
|
to_delete: false,
|
||
|
).order('position ASC')
|
||
|
attributes = []
|
||
|
result.each {|item|
|
||
|
data = {
|
||
|
name: item.name,
|
||
|
display: item.display,
|
||
|
tag: item.data_type,
|
||
|
#:null => item.null,
|
||
|
}
|
||
|
if item.screens
|
||
|
data[:screen] = {}
|
||
|
item.screens.each {|screen, roles_options|
|
||
|
data[:screen][screen] = {}
|
||
|
roles_options.each {|role, options|
|
||
|
if role == '-all-'
|
||
|
data[:screen][screen] = options
|
||
|
elsif user && user.role?(role)
|
||
|
data[:screen][screen] = options
|
||
|
end
|
||
|
}
|
||
|
}
|
||
|
end
|
||
|
if item.data_option
|
||
|
data = data.merge(item.data_option.symbolize_keys)
|
||
|
end
|
||
|
attributes.push data
|
||
|
}
|
||
|
attributes
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
get user based list of object attributes as hash
|
||
|
|
||
|
attribute_list = ObjectManager::Attribute.by_object_as_hash('Ticket', user)
|
||
|
|
||
|
returns:
|
||
|
|
||
|
{
|
||
|
'api_key' => { name: 'api_key', display: 'API KEY', tag: 'input', null: true, edit: true, maxlength: 32 },
|
||
|
'api_ip_regexp' => { name: 'api_ip_regexp', display: 'API IP RegExp', tag: 'input', null: true, edit: true },
|
||
|
'api_ip_max' => { name: 'api_ip_max', display: 'API IP Max', tag: 'input', null: true, edit: true },
|
||
|
}
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.by_object_as_hash(object, user)
|
||
|
list = by_object(object, user)
|
||
|
hash = {}
|
||
|
list.each {|item|
|
||
|
hash[ item[:name] ] = item
|
||
|
}
|
||
|
hash
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
discard migration changes
|
||
|
|
||
|
ObjectManager::Attribute.discard_changes
|
||
|
|
||
|
returns
|
||
|
|
||
|
true|false
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.discard_changes
|
||
|
ObjectManager::Attribute.where('to_create = ?', true).each(&:destroy)
|
||
|
ObjectManager::Attribute.where('to_delete = ?', true).each {|attribute|
|
||
|
attribute.to_delete = false
|
||
|
attribute.save
|
||
|
}
|
||
|
true
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
check if we have pending migrations of attributes
|
||
|
|
||
|
ObjectManager::Attribute.pending_migration?
|
||
|
|
||
|
returns
|
||
|
|
||
|
true|false
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.pending_migration?
|
||
|
return false if migrations.empty?
|
||
|
true
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
get list of pending attributes migrations
|
||
|
|
||
|
ObjectManager::Attribute.migrations
|
||
|
|
||
|
returns
|
||
|
|
||
|
[record1, record2, ...]
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.migrations
|
||
|
ObjectManager::Attribute.where('to_create = ? OR to_migrate = ? OR to_delete = ?', true, true, true)
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
|
||
|
start migration of pending attribute migrations
|
||
|
|
||
|
ObjectManager::Attribute.migration_execute
|
||
|
|
||
|
returns
|
||
|
|
||
|
[record1, record2, ...]
|
||
|
|
||
|
=end
|
||
|
|
||
|
def self.migration_execute
|
||
|
|
||
|
# check if field already exists
|
||
|
execute_count = 0
|
||
|
migrations.each {|attribute|
|
||
|
model = Kernel.const_get(attribute.object_lookup.name)
|
||
|
|
||
|
# remove field
|
||
|
if attribute.to_delete
|
||
|
if model.column_names.include?(attribute.name)
|
||
|
ActiveRecord::Migration.remove_column model.table_name, attribute.name
|
||
|
model.reset_column_information
|
||
|
execute_count += 1
|
||
|
end
|
||
|
attribute.destroy
|
||
|
next
|
||
|
end
|
||
|
|
||
|
data_type = nil
|
||
|
if attribute.data_type =~ /^input|select|richtext|textarea$/
|
||
|
data_type = :string
|
||
|
elsif attribute.data_type =~ /^boolean|active$/
|
||
|
data_type = :boolean
|
||
|
elsif attribute.data_type =~ /^datetime$/
|
||
|
data_type = :datetime
|
||
|
elsif attribute.data_type =~ /^date$/
|
||
|
data_type = :date
|
||
|
end
|
||
|
|
||
|
# change field
|
||
|
if model.column_names.include?(attribute.name)
|
||
|
if attribute.data_type =~ /^input|select|richtext|textarea$/
|
||
|
ActiveRecord::Migration.change_column(
|
||
|
model.table_name,
|
||
|
attribute.name,
|
||
|
data_type,
|
||
|
limit: attribute.data_option[:maxlength],
|
||
|
null: true
|
||
|
)
|
||
|
elsif attribute.data_type =~ /^boolean|active|datetime|date$/
|
||
|
ActiveRecord::Migration.change_column(
|
||
|
model.table_name,
|
||
|
attribute.name,
|
||
|
data_type,
|
||
|
default: attribute.data_option[:default],
|
||
|
null: true
|
||
|
)
|
||
|
else
|
||
|
raise "Unknown attribute.data_type '#{attribute.data_type}', can't update attribute"
|
||
|
end
|
||
|
|
||
|
# restart processes
|
||
|
attribute.to_migrate = false
|
||
|
attribute.save!
|
||
|
model.reset_column_information
|
||
|
execute_count += 1
|
||
|
next
|
||
|
end
|
||
|
|
||
|
# create field
|
||
|
if attribute.data_type =~ /^input|select|richtext|textarea$/
|
||
|
ActiveRecord::Migration.add_column(
|
||
|
model.table_name,
|
||
|
attribute.name,
|
||
|
data_type,
|
||
|
limit: attribute.data_option[:maxlength],
|
||
|
null: true
|
||
|
)
|
||
|
elsif attribute.data_type =~ /^boolean|active$/
|
||
|
ActiveRecord::Migration.add_column(
|
||
|
model.table_name,
|
||
|
attribute.name,
|
||
|
data_type,
|
||
|
default: attribute.data_option[:default],
|
||
|
null: false
|
||
|
)
|
||
|
elsif attribute.data_type =~ /^datetime|date$/
|
||
|
ActiveRecord::Migration.add_column(
|
||
|
model.table_name,
|
||
|
attribute.name,
|
||
|
data_type,
|
||
|
default: attribute.data_option[:default],
|
||
|
null: true
|
||
|
)
|
||
|
else
|
||
|
raise "Unknown attribute.data_type '#{attribute.data_type}', can't create attribute"
|
||
|
end
|
||
|
|
||
|
# restart processes
|
||
|
attribute.to_create = false
|
||
|
attribute.to_migrate = false
|
||
|
attribute.to_delete = false
|
||
|
attribute.save!
|
||
|
|
||
|
model.reset_column_information
|
||
|
execute_count += 1
|
||
|
}
|
||
|
|
||
|
# sent reload to clients
|
||
|
if execute_count != 0
|
||
|
AppVersion.set(true)
|
||
|
end
|
||
|
true
|
||
|
end
|
||
|
|
||
|
def check_name
|
||
|
if name
|
||
|
return true if name !~ /_(id|ids)$/i && name !~ /^id$/i
|
||
|
end
|
||
|
raise "Name can't get used, *_id and *_ids are not allowed"
|
||
|
end
|
||
|
|
||
|
def check_editable
|
||
|
return if editable
|
||
|
raise 'Attribute not editable!'
|
||
|
end
|
||
|
|
||
|
def check_datatype
|
||
|
if !data_type
|
||
|
raise 'Need data_type param'
|
||
|
end
|
||
|
if data_type !~ /^(input|user_autocompletion|checkbox|select|datetime|date|tag|richtext|textarea|integer|autocompletion_ajax|boolean|user_permission|active)$/
|
||
|
raise "Invalid data_type param '#{data_type}'"
|
||
|
end
|
||
|
|
||
|
if !data_option
|
||
|
raise 'Need data_type param'
|
||
|
end
|
||
|
if data_option[:null].nil?
|
||
|
raise 'Need data_option[:null] param with true or false'
|
||
|
end
|
||
|
|
||
|
# validate data_option
|
||
|
if data_type == 'input'
|
||
|
raise 'Need data_option[:type] param' if !data_option[:type]
|
||
|
raise "Invalid data_option[:type] param '#{data_option[:type]}'" if data_option[:type] !~ /^(text|password|phone|fax|email|url)$/
|
||
|
raise 'Need data_option[:maxlength] param' if !data_option[:maxlength]
|
||
|
raise "Invalid data_option[:maxlength] param #{data_option[:maxlength]}" if data_option[:maxlength].to_s !~ /^\d+?$/
|
||
|
end
|
||
|
|
||
|
if data_type == 'integer'
|
||
|
[:min, :max].each {|item|
|
||
|
raise "Need data_option[#{item.inspect}] param" if !data_option[item]
|
||
|
raise "Invalid data_option[#{item.inspect}] param #{data_option[item]}" if data_option[item].to_s !~ /^\d+?$/
|
||
|
}
|
||
|
end
|
||
|
|
||
|
if data_type == 'select' || data_type == 'checkbox'
|
||
|
raise 'Need data_option[:default] param' if data_option[:default].nil?
|
||
|
raise 'Invalid data_option[:options] or data_option[:relation] param' if data_option[:options].nil? && data_option[:relation].nil?
|
||
|
end
|
||
|
|
||
|
if data_type == 'boolean'
|
||
|
raise 'Need data_option[:default] param true|false' if data_option[:default].nil?
|
||
|
raise 'Invalid data_option[:options] param' if data_option[:options].nil?
|
||
|
end
|
||
|
|
||
|
if data_type == 'datetime'
|
||
|
raise 'Need data_option[:future] param true|false' if data_option[:future].nil?
|
||
|
raise 'Need data_option[:past] param true|false' if data_option[:past].nil?
|
||
|
raise 'Need data_option[:diff] param in hours' if data_option[:diff].nil?
|
||
|
end
|
||
|
|
||
|
if data_type == 'date'
|
||
|
raise 'Need data_option[:future] param true|false' if data_option[:future].nil?
|
||
|
raise 'Need data_option[:past] param true|false' if data_option[:past].nil?
|
||
|
raise 'Need data_option[:diff] param in days' if data_option[:diff].nil?
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|