require 'neography' require 'neoid/version' require 'neoid/config' require 'neoid/model_config' require 'neoid/model_additions' require 'neoid/search_session' require 'neoid/node' require 'neoid/relationship' require 'neoid/batch' require 'neoid/database_cleaner' require 'neoid/railtie' if defined?(Rails) module Neoid DEFAULT_FULLTEXT_SEARCH_INDEX_NAME = :neoid_default_search_index NODE_AUTO_INDEX_NAME = 'node_auto_index' RELATIONSHIP_AUTO_INDEX_NAME = 'relationship_auto_index' UNIQUE_ID_KEY = 'neoid_unique_id' class << self attr_accessor :db attr_accessor :logger attr_accessor :ref_node attr_accessor :env_loaded attr_reader :config def node_models @node_models ||= [] end def relationship_models @relationship_models ||= [] end def config @config ||= begin c = Neoid::Config.new # default c.enable_subrefs = true c.enable_per_model_indexes = false c end end def configure yield config end def initialize_all @env_loaded = true logger.info "Neoid initialize_all" initialize_relationships initialize_server end def initialize_server initialize_auto_index initialize_subrefs initialize_per_model_indexes end def db raise "Must set Neoid.db with a Neography::Rest instance" unless @db @db end def batch(options={}, &block) Neoid::Batch.new(options, &block).run end def logger @logger ||= Logger.new(ENV['NEOID_LOG'] ? ENV['NEOID_LOG_FILE'] || $stdout : '/dev/null') end def ref_node @ref_node ||= Neography::Node.load(Neoid.db.get_root['self']) end def reset_cached_variables initialize_subrefs end def clean_db(confirm) puts "must call with confirm: Neoid.clean_db(:yes_i_am_sure)" and return unless confirm == :yes_i_am_sure Neoid::NeoDatabaseCleaner.clean_db end def enabled=(flag) Thread.current[:neoid_enabled] = flag end def enabled flag = Thread.current[:neoid_enabled] # flag should be set by the middleware. in case it wasn't (non-rails app or console), default it to true flag.nil? ? true : flag end alias enabled? enabled def use(flag=true) old, self.enabled = enabled?, flag yield if block_given? ensure self.enabled = old end def execute_script_or_add_to_batch(gremlin_query, script_vars) if Neoid::Batch.current_batch # returns a SingleResultPromiseProxy! Neoid::Batch.current_batch << [:execute_script, gremlin_query, script_vars] else value = Neoid.db.execute_script(gremlin_query, script_vars) value = yield(value) if block_given? Neoid::BatchPromiseProxy.new(value) end end # create a fulltext index if not exists def ensure_default_fulltext_search_index Neoid.db.create_node_index(DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, 'fulltext', 'lucene') unless (indexes = Neoid.db.list_node_indexes) && indexes[DEFAULT_FULLTEXT_SEARCH_INDEX_NAME] end def search(types, term, options = {}) options = options.reverse_merge(limit: 15) types = [*types] query = [] types.each do |type| query_for_type = [] query_for_type << "ar_type:#{type.name}" case term when String search_in_fields = type.neoid_config.search_options.fulltext_fields.keys next if search_in_fields.empty? query_for_type << search_in_fields.map{ |field| generate_field_query(field, term, true) }.join(" OR ") when Hash term.each do |field, value| query_for_type << generate_field_query(field, value, false) end end query << "(#{query_for_type.join(") AND (")})" end query = "(#{query.join(") OR (")})" logger.info "Neoid query #{query}" gremlin_query = <<-GREMLIN #{options[:before_query]} idx = g.getRawGraph().index().forNodes('#{DEFAULT_FULLTEXT_SEARCH_INDEX_NAME}') hits = idx.query('#{sanitize_query_for_gremlin(query)}') hits = #{options[:limit] ? "hits.take(#{options[:limit]})" : "hits"} #{options[:after_query]} GREMLIN logger.info "[NEOID] search:\n#{gremlin_query}" results = Neoid.db.execute_script(gremlin_query) SearchSession.new(results, *types) end private def sanitize_term(term) # TODO - case sensitive? term.downcase end def sanitize_query_for_gremlin(query) # TODO - case sensitive? query.gsub("'", "\\\\'") end def generate_field_query(field, term, fulltext = false) term = term.to_s if term return "" if term.nil? || term.empty? fulltext = fulltext ? "_fulltext" : nil "(" + term.split(/\s+/).reject(&:empty?).map{ |t| "#{field}#{fulltext}:#{sanitize_term(t)}" }.join(" AND ") + ")" end def initialize_relationships logger.info "Neoid initialize_relationships" relationship_models.each do |rel_model| Relationship.initialize_relationship(rel_model) end end def initialize_auto_index logger.info "Neoid initialize_auto_index" Neoid.db.set_node_auto_index_status(true) Neoid.db.add_node_auto_index_property(UNIQUE_ID_KEY) Neoid.db.set_relationship_auto_index_status(true) Neoid.db.add_relationship_auto_index_property(UNIQUE_ID_KEY) end def initialize_subrefs return unless config.enable_subrefs node_models.each do |klass| klass.reset_neo_subref_node end logger.info "Neoid initialize_subrefs" batch do node_models.each(&:neo_subref_node) end.then do |results| node_models.zip(results).each do |klass, subref| klass.neo_subref_node = subref end end end def initialize_per_model_indexes return unless config.enable_per_model_indexes logger.info "Neoid initialize_subrefs" batch do node_models.each(&:neo_model_index) end end end end