From 97911a41228e1319725c2b070585cea18dc2763b Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 19 May 2016 11:21:50 +0200 Subject: [PATCH] Init version of object manager. --- app/models/object_manager/attribute.rb | 538 +++++++++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 app/models/object_manager/attribute.rb diff --git a/app/models/object_manager/attribute.rb b/app/models/object_manager/attribute.rb new file mode 100644 index 000000000..211bc3150 --- /dev/null +++ b/app/models/object_manager/attribute.rb @@ -0,0 +1,538 @@ +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