module Gummi module DbLayer module Document extend ActiveSupport::Concern included do include Virtus.model include Gummi::DbLayer::Document::Attributes end # ––––––––––––– # Class Methods # ––––––––––––– module ClassMethods # –––––––––––––––––––––– # Public Persistence API # –––––––––––––––––––––– def create(*args) instance = new(*args) instance.create ? instance : nil end def get(*args) get! *args rescue ::Elasticsearch::Transport::Transport::Errors::NotFound nil end def delete(*args) delete! *args rescue ::Elasticsearch::Transport::Transport::Errors::NotFound nil end def delete_children_by_query(parent_id, children_query) parent_id_query = { term: { _parent: parent_id } } query = { bool: { must: [parent_id_query, children_query] } } delete_by_query query end def delete_by_query(query) options = { index: index.name, type: document_type, body: query } Hashie::Mash.new client.delete_by_query options end # ––––––––––––––––––––– # Public Conversion API # ––––––––––––––––––––– def hits_to_documents(hits) documents = [hits].flatten.map { |hit| hit_to_document(hit) } documents.length > 1 ? documents : documents.first end def hit_to_document(hit) attributes = { id: hit._id, version: hit._version } attributes.merge!(parent_id_attribute_name => hit.fields._parent) if hit.fields && hit.fields._parent attributes.merge! hit._source if hit._source self.new attributes end # –––––––––––––––––––––––––––––– # Index and Document definitions # –––––––––––––––––––––––––––––– def index(*args) @index = args.first unless args.empty? @index || Gummi::DbLayer::DefaultIndex end def document_type(*args) @document_type = args.first.to_sym unless args.empty? @document_type || name.demodulize.underscore.to_sym end def parent(model) parent_document_type model.document_type end def parent_document_type(*args) @parent_document_type = args.first unless args.empty? parent_id_attribute_name "#{@parent_document_type}_id".to_sym if @parent_document_type && !parent_id_attribute_name @parent_document_type end def parent_id_attribute_name(*args) unless args.empty? @parent_id_attribute_name = args.first attr_accessor @parent_id_attribute_name end @parent_id_attribute_name end # –––––––––––––––– # Public Index API # –––––––––––––––– def sync_mapping! client.indices.put_mapping creation_options end def creation_options result = { index: index.name, type: document_type, body: { document_type => { properties: mapping, } } } result[:body][document_type].merge!(_parent: { type: parent_document_type }) if parent_document_type.present? result end # ––––––––––––––––– # Public Search API # ––––––––––––––––– def new_filtered_search(options = {}) Gummi::DbLayer::Document::Search::Filtered.new default_search_options.merge(options) end def new_raw_search(options = {}) Gummi::DbLayer::Document::Search::Raw.new default_search_options.merge(options) end def default_search_options { document_class: self, index: index.name, type: document_type, } end # ––––––––––––––––––––––––––– # Internal Backend Connection # ––––––––––––––––––––––––––– def client Gummi::API.client end private # –––––––––––––––––––––––– # Internal Persistence API # –––––––––––––––––––––––– def get!(id, parent_id = nil) options = { index: index.name, type: document_type, id: id, fields: %w{ _source _parent } } if parent_id options.merge! parent: parent_id elsif parent_document_type raise ArgumentError, "The parent_id attribute is required for getting #{name} from Elastic Search" end response = ActiveSupport::Notifications.instrument "search.elasticsearch", name: "Document#get!", search: options do Hashie::Mash.new client.get options end hit_to_document response end def delete!(id, parent_id = nil) options = { index: index.name, type: document_type, id: id } if parent_id options.merge! parent: parent_id elsif parent_document_type raise ArgumentError, "The parent_id attribute is required for getting #{name} from Elastic Search" end response = ActiveSupport::Notifications.instrument "search.elasticsearch", name: "Document#delete!", search: options do Hashie::Mash.new client.delete options end response.ok && response.found end end # –––––––––––––––– # Instance Methods # –––––––––––––––– attr_accessor :id attr_accessor :version # –––––––––––––––––––––– # Public Persistence API # –––––––––––––––––––––– def create(method = :create) attributes = self.attributes if parent_id_attribute_name && parent_id = self.send(parent_id_attribute_name) options = { parent: parent_id } else options = {} end raise ArgumentError unless [:create, :index].include?(method) opts = options.merge(index: index.name, type: document_type, id: self.id, body: attributes) response = ActiveSupport::Notifications.instrument "search.elasticsearch", name: "Document#create(#{method})", search: opts do Hashie::Mash.new client.send(method, opts) end if response.ok self.id = response._id self.version = response._version true else false end end def overwrite create :index end private # –––––––––––––––––––– # Class method proxies # –––––––––––––––––––– def index self.class.index end def document_type self.class.document_type end def parent_id_attribute_name self.class.parent_id_attribute_name end def client self.class.client end end end end