diff --git a/app/models/application_model.rb b/app/models/application_model.rb index 7814c10a6..7be8540c6 100644 --- a/app/models/application_model.rb +++ b/app/models/application_model.rb @@ -4,6 +4,7 @@ class ApplicationModel < ActiveRecord::Base include ApplicationModel::Assets include ApplicationModel::HistoryLogBase include ApplicationModel::ActivityStreamBase + include ApplicationModel::SearchIndexBase self.abstract_class = true @@ -23,9 +24,13 @@ class ApplicationModel < ActiveRecord::Base after_update :history_update after_destroy :history_destroy + after_create :search_index_update + after_update :search_index_update + after_destroy :search_index_destroy + # create instance accessor class << self - attr_accessor :activity_stream_support_config, :history_support_config + attr_accessor :activity_stream_support_config, :history_support_config, :search_index_support_config end attr_accessor :history_changes_last_done @@ -453,28 +458,65 @@ class OwnModel < ApplicationModel ) end - private - =begin -check string/varchar size and cut them if needed +serve methode to configure and enable search index support for this model + +class Model < ApplicationModel + search_index_support :ignore_attributes => { + :create_article_type_id => true, + :create_article_sender_id => true, + :article_count => true, + } + +end =end - def check_limits - self.attributes.each {|attribute| - next if !self[ attribute[0] ] - next if self[ attribute[0] ].class != String - next if self[ attribute[0] ].empty? - column = self.class.columns_hash[ attribute[0] ] - limit = column.limit - if column && limit - current_length = attribute[1].to_s.length - if limit < current_length - puts "WARNING: cut string because of database length #{self.class.to_s}.#{attribute[0]}(#{limit} but is #{current_length}:#{attribute[1].to_s})" - self[attribute[0]] = attribute[1][ 0, limit ] - end - end + def self.search_index_support(data = {}) + @search_index_support_config = data + end + +=begin + +update search index, if configured - will be executed automatically + + model = Model.find(123) + model.search_index_update + +=end + + def search_index_update + return if !self.class.search_index_support_config + search_index_update_backend + end + +=begin + +delete search index object, will be executed automatically + + model = Model.find(123) + model.search_index_destroy + +=end + + def search_index_destroy + return if !self.class.search_index_support_config + SearchIndexBackend.remove( self.class.to_s, self.id ) + end + +=begin + +reload search index with full data + + Model.search_index_reload + +=end + + def self.search_index_reload + return if !@search_index_support_config + self.all.each { |item| + item.search_index_update_backend } end @@ -557,7 +599,7 @@ delete object activity stream, will be executed automatically =end def activity_stream_destroy - return if !@activity_stream_support_config + return if !self.class.activity_stream_support_config ActivityStream.remove( self.class.to_s, self.id ) end @@ -703,10 +745,35 @@ delete object history, will be executed automatically =end def history_destroy - return if !@history_support_config + return if !self.class.history_support_config History.remove( self.class.to_s, self.id ) end + private + +=begin + +check string/varchar size and cut them if needed + +=end + + def check_limits + self.attributes.each {|attribute| + next if !self[ attribute[0] ] + next if self[ attribute[0] ].class != String + next if self[ attribute[0] ].empty? + column = self.class.columns_hash[ attribute[0] ] + limit = column.limit + if column && limit + current_length = attribute[1].to_s.length + if limit < current_length + puts "WARNING: cut string because of database length #{self.class.to_s}.#{attribute[0]}(#{limit} but is #{current_length}:#{attribute[1].to_s})" + self[attribute[0]] = attribute[1][ 0, limit ] + end + end + } + end + =begin destory object dependencies, will be executed automatically diff --git a/app/models/application_model/search_index_base.rb b/app/models/application_model/search_index_base.rb new file mode 100644 index 000000000..57535885f --- /dev/null +++ b/app/models/application_model/search_index_base.rb @@ -0,0 +1,109 @@ +# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/ + +module ApplicationModel::SearchIndexBase + +=begin + +collect data to index and send to backend + + ticket = Ticket.find(123) + result = ticket.search_index_update_backend + +returns + + result = true # false + +=end + + def search_index_update_backend + return if !self.class.search_index_support_config + + # default ignored attributes + ignore_attributes = { + :created_at => true, + :updated_at => true, + :created_by_id => true, + :updated_by_id => true, + :active => true, + } + if self.class.search_index_support_config[:ignore_attributes] + self.class.search_index_support_config[:ignore_attributes].each {|key, value| + ignore_attributes[key] = value + } + end + + # remove ignored attributes + attributes = self.attributes + ignore_attributes.each {|key, value| + next if value != true + attributes.delete( key.to_s ) + } + + # fill up with search data + attributes = search_index_attribute_lookup(attributes, self) + return if !attributes + + # update backend + if self.class.column_names.include? 'active' + if self.active + SearchIndexBackend.add( self.class.to_s, attributes ) + else + SearchIndexBackend.remove( self.class.to_s, self.id ) + end + else + SearchIndexBackend.add( self.class.to_s, attributes ) + end + end + + private + +=begin + +lookup name of ref. objects + + attributes = search_index_attribute_lookup(attributes, Ticket) + +returns + + attributes # object with lookup data + +=end + + def search_index_attribute_lookup(attributes, ref_object) + attributes_new = {} + attributes.each {|key, value| + next if !value + + # get attribute name + attribute_name = key.to_s + next if attribute_name[-3,3] != '_id' + attribute_name = attribute_name[ 0, attribute_name.length-3 ] + + # check if attribute method exists + next if !ref_object.respond_to?( attribute_name ) + + # check if method has own class + relation_class = ref_object.send(attribute_name).class + next if !relation_class + + # lookup ref object + relation_model = relation_class.lookup( :id => value ) + next if !relation_model + + # get name of ref object + value = nil + if relation_model['name'] + value = relation_model['name'] + elsif relation_model.respond_to?('fullname') + value = relation_model.send('fullname') + end + next if !value + + # save name of ref object + attributes_new[ attribute_name ] = value + attributes.delete(key) + } + attributes_new.merge(attributes) + end + +end \ No newline at end of file diff --git a/app/models/organization.rb b/app/models/organization.rb index 0f4b57015..6980da61b 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -9,5 +9,6 @@ class Organization < ApplicationModel activity_stream_support :role => 'Admin' history_support + search_index_support end \ No newline at end of file diff --git a/app/models/ticket.rb b/app/models/ticket.rb index e24695ff7..65bbd85e5 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -10,6 +10,8 @@ class Ticket < ApplicationModel include Ticket::HistoryLog require 'ticket/activity_stream_log' include Ticket::ActivityStreamLog + require 'ticket/search_index' + include Ticket::SearchIndex extend Ticket::Search before_create :check_generate, :check_defaults @@ -31,6 +33,12 @@ class Ticket < ApplicationModel :article_count => true, } + search_index_support :ignore_attributes => { + :create_article_type_id => true, + :create_article_sender_id => true, + :article_count => true, + } + belongs_to :group has_many :articles, :class_name => 'Ticket::Article', :after_add => :cache_update, :after_remove => :cache_update belongs_to :organization diff --git a/app/models/ticket/search_index.rb b/app/models/ticket/search_index.rb new file mode 100644 index 000000000..0766fcebd --- /dev/null +++ b/app/models/ticket/search_index.rb @@ -0,0 +1,67 @@ +# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/ + +module Ticket::SearchIndex + +=begin + +log activity for this object + + ticket = Ticket.find(123) + result = ticket.search_index_update_backend + +returns + + result = true # false + +=end + + def search_index_update_backend + return if !self.class.search_index_support_config + + # default ignored attributes + ignore_attributes = { + :created_at => true, + :updated_at => true, + :created_by_id => true, + :updated_by_id => true, + :active => true, + } + if self.class.search_index_support_config[:ignore_attributes] + self.class.search_index_support_config[:ignore_attributes].each {|key, value| + ignore_attributes[key] = value + } + end + + attributes = self.attributes + ignore_attributes.each {|key, value| + next if value != true + attributes.delete( key.to_s ) + } + attributes = search_index_attribute_lookup( attributes, self ) + + # add article data + articles = Ticket::Article.where( :ticket_id => self.id ) + attributes['articles_all'] = [] + attributes['articles_external'] = [] + articles.each {|article| + article_attributes = article.attributes + article_attributes.delete('created_by_id') + article_attributes.delete('updated_by_id') + article_attributes.delete('updated_at') + article_attributes.delete('references') + article_attributes.delete('message_id_md5') + article_attributes.delete('message_id') + article_attributes.delete('in_reply_to') + article_attributes.delete('ticket_id') + article_attributes = search_index_attribute_lookup( article_attributes, article ) + attributes['articles_all'].push article_attributes + if !article.internal + attributes['articles_external'].push article_attributes + end + } + + return if !attributes + SearchIndexBackend.add(self.class.to_s, attributes) + end + +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index b90cce47f..8de54f8c7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,6 +36,17 @@ class User < ApplicationModel :image_source => true, } ) + search_index_support( + :ignore_attributes => { + :password => true, + :image => true, + :image_source => true, + :source => true, + :login_failed => true, + :preferences => true, + :locale => true, + } + ) =begin diff --git a/lib/search_index_backend.rb b/lib/search_index_backend.rb new file mode 100644 index 000000000..0ec4663ad --- /dev/null +++ b/lib/search_index_backend.rb @@ -0,0 +1,82 @@ +# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/ + +class SearchIndexBackend + @@index = "zammad_#{Rails.env}" + @@url = 'http://127.0.0.1:9000' + @@user = 'elasticsearch' + @@pw = 'zammad' + +=begin + +add new object to search index + + SearchIndexBackend.add( 'Ticket', some_data_object ) + +=end + + def self.add(type, data) + + url = "#{@@url}/#{@@index}/#{type}/#{data[:id]}" + + puts "# curl -X POST \"#{url}\" -d '#{data.to_json}'" + + conn = Faraday.new( :url => url ) + if @@user && @@pw + conn.basic_auth( @@user, @@pw ) + end + + response = conn.post do |req| + req.url url + req.headers['Content-Type'] = 'application/json' + req.body = data.to_json + end +# puts response.body.to_s + puts "# #{response.status.to_s}" + return true if response.success? + data = JSON.parse( response.body ) + return { :data => data, :response => response } + end + +=begin + +remove whole data from index + + SearchIndexBackend.remove( 'Ticket', 123 ) + + SearchIndexBackend.remove( 'Ticket' ) + +=end + + def self.remove( type, o_id = nil ) + if o_id + url = "#{@@url}/#{@@index}/#{type}/#{o_id}" + else + url = "#{@@url}/#{@@index}/#{type}" + end + + puts "# curl -X DELETE \"#{url}\"" + + conn = Faraday.new( :url => url ) + if @@user && @@pw + conn.basic_auth( @@user, @@pw ) + end + response = conn.delete url +# puts response.body.to_s + puts "# #{response.status.to_s}" + return true if response.success? + data = JSON.parse( response.body ) + return { :data => data, :response => response } + end + +=begin + +return all activity entries of an user + + result = SearchIndexBackend.search( user ) + +=end + + def self.search(user,limit) + end + +end \ No newline at end of file