From 9916a9f272e82a4d4383ce4b172b40ee09e273a7 Mon Sep 17 00:00:00 2001 From: Alex Willemsma Date: Mon, 6 Jan 2014 16:46:37 -1000 Subject: [PATCH 1/6] Initial work on tweaking the gem to support multiple neo4j instances. --- lib/neoid.rb | 226 ++++------------------------------ lib/neoid/batch.rb | 19 +-- lib/neoid/database_cleaner.rb | 6 +- lib/neoid/instance.rb | 226 ++++++++++++++++++++++++++++++++++ lib/neoid/model_additions.rb | 10 +- lib/neoid/node.rb | 46 ++++--- lib/neoid/railtie.rb | 2 +- lib/neoid/relationship.rb | 30 +++-- spec/spec_helper.rb | 10 +- 9 files changed, 320 insertions(+), 255 deletions(-) create mode 100644 lib/neoid/instance.rb diff --git a/lib/neoid.rb b/lib/neoid.rb index 8d0e6ec..ef7d731 100644 --- a/lib/neoid.rb +++ b/lib/neoid.rb @@ -1,5 +1,6 @@ require 'neography' require 'neoid/version' +require 'neoid/instance' require 'neoid/config' require 'neoid/model_config' require 'neoid/model_additions' @@ -17,223 +18,38 @@ module Neoid 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 + attr_accessor :default_connection_name - 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 + def connections + @connections ||= {} end - alias enabled? enabled - def use(flag=true) - old, self.enabled = enabled?, flag - yield if block_given? - ensure - self.enabled = old - end + def connection(name = nil) + if !name || name == :default + name = default_connection_name + if !name && connections.size == 1 + return connections.first + end + 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] + if connections.key? name + connections[name] else - value = Neoid.db.execute_script(gremlin_query, script_vars) - - value = yield(value) if block_given? - - Neoid::BatchPromiseProxy.new(value) + raise ArgumentError.new("No Neo4j connection with name #{name}.") 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] + def add_connection(name, connection, &block) + instance = Neoid::Instance.new(connection) + instance.configure(&block) if block_given? + connections[name] = instance end - def search(types, term, options = {}) - options = options.reverse_merge(limit: 15,match_type: "AND") - - 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, options[:match_type]) }.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 (")})" + def initialize_all + connections.each do |name, instance| + instance.initialize_all 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, match_type = "AND") - term = term.to_s if term - return "" if term.nil? || term.empty? - - fulltext = fulltext ? "_fulltext" : nil - valid_match_types = %w( AND OR ) - match_type = valid_match_types.delete(match_type) - raise "Invalid match_type option. Valid values are #{valid_match_types.join(',')}" unless match_type - - "(" + term.split(/\s+/).reject(&:empty?).map{ |t| "#{field}#{fulltext}:#{sanitize_term(t)}" }.join(" #{match_type} ") + ")" - 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 diff --git a/lib/neoid/batch.rb b/lib/neoid/batch.rb index d9ccb5b..9670246 100644 --- a/lib/neoid/batch.rb +++ b/lib/neoid/batch.rb @@ -20,7 +20,7 @@ def self.reset_current_batch Thread.current[:neoid_current_batch] = nil end - def initialize(options={}, &block) + def initialize(instance, options={}, &block) if options.respond_to?(:call) && !block block = options options = {} @@ -28,6 +28,7 @@ def initialize(options={}, &block) options.reverse_merge!(self.class.default_options) + @instance = instance @options = options @block = block end @@ -71,7 +72,7 @@ def run self.class.reset_current_batch end - Neoid.logger.info "Neoid batch (#{commands.length} commands)" + @instance.logger.info "Neoid batch (#{commands.length} commands)" flush_batch @@ -83,12 +84,12 @@ def flush_batch return [] if commands.empty? current_results = nil - # results = Neoid.db.batch(*commands).collect { |result| result['body'] } + # results = @instance.db.batch(*commands).collect { |result| result['body'] } benchmark = Benchmark.measure { - current_results = Neoid.db.batch(*commands).collect { |result| result['body'] } + current_results = @instance.db.batch(*commands).collect { |result| result['body'] } } - Neoid.logger.info "Neoid batch (#{commands.length} commands) - #{benchmark}" + @instance.logger.info "Neoid batch (#{commands.length} commands) - #{benchmark}" commands.clear process_results(current_results) @@ -133,11 +134,11 @@ def then end # returned from adding (<<) an item to a batch in a batch block: - # Neoid.batch { |batch| (batch << [:neography_command, param]).is_a?(SingleResultPromiseProxy) } + # @instance.batch { |batch| (batch << [:neography_command, param]).is_a?(SingleResultPromiseProxy) } # so a `.then` can be chained: - # Neoid.batch { |batch| (batch << [:neography_command, param]).then { |result| puts result } } + # @instance.batch { |batch| (batch << [:neography_command, param]).then { |result| puts result } } # the `then` is called once the batch is flushed with the result of the single job in the batch - # it proxies all methods to the result, so in case it is returned (like in Neoid.execute_script_or_add_to_batch) + # it proxies all methods to the result, so in case it is returned (like in @instance.execute_script_or_add_to_batch) # the result of the method will be proxied to the result from the batch. See Node#neo_save class SingleResultPromiseProxy def initialize(*args) @@ -165,4 +166,4 @@ def perform(result) @then.call(result) end end -end \ No newline at end of file +end diff --git a/lib/neoid/database_cleaner.rb b/lib/neoid/database_cleaner.rb index 39ad63a..228bc76 100644 --- a/lib/neoid/database_cleaner.rb +++ b/lib/neoid/database_cleaner.rb @@ -1,7 +1,9 @@ module Neoid class NeoDatabaseCleaner - def self.clean_db(start_node = Neoid.db.get_root) - Neoid.db.execute_script <<-GREMLIN + def self.clean_db(instance, start_node = nil) + puts instance.inspect + start_node ||= instance.db.get_root + instance.db.execute_script <<-GREMLIN g.V.toList().each { if (it.id != 0) g.removeVertex(it) } g.indices.each { g.dropIndex(it.indexName); } GREMLIN diff --git a/lib/neoid/instance.rb b/lib/neoid/instance.rb new file mode 100644 index 0000000..87cb359 --- /dev/null +++ b/lib/neoid/instance.rb @@ -0,0 +1,226 @@ +module Neoid + class Instance + attr_accessor :db + attr_accessor :logger + attr_accessor :ref_node + attr_accessor :env_loaded + attr_reader :config + + def initialize(connection) + self.db = connection + end + + 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::Instance.db with a Neography::Rest instance" unless @db + @db + end + + def batch(options={}, &block) + Neoid::Batch.new(self, 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(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(self) + 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 = 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 + db.create_node_index(Neoid::DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, 'fulltext', 'lucene') unless (indexes = db.list_node_indexes) && indexes[Neoid::DEFAULT_FULLTEXT_SEARCH_INDEX_NAME] + end + + def search(types, term, options = {}) + options = options.reverse_merge(limit: 15,match_type: "AND") + + 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, options[:match_type]) }.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('#{Neoid::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 = 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, match_type = "AND") + term = term.to_s if term + return "" if term.nil? || term.empty? + + fulltext = fulltext ? "_fulltext" : nil + valid_match_types = %w( AND OR ) + match_type = valid_match_types.delete(match_type) + raise "Invalid match_type option. Valid values are #{valid_match_types.join(',')}" unless match_type + + "(" + term.split(/\s+/).reject(&:empty?).map{ |t| "#{field}#{fulltext}:#{sanitize_term(t)}" }.join(" #{match_type} ") + ")" + 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" + db.set_node_auto_index_status(true) + db.add_node_auto_index_property(Neoid::UNIQUE_ID_KEY) + + db.set_relationship_auto_index_status(true) + db.add_relationship_auto_index_property(Neoid::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 diff --git a/lib/neoid/model_additions.rb b/lib/neoid/model_additions.rb index 14b3a4a..d27df8b 100644 --- a/lib/neoid/model_additions.rb +++ b/lib/neoid/model_additions.rb @@ -2,7 +2,11 @@ module Neoid module ModelAdditions module ClassMethods attr_reader :neoid_config - + + def use_neo4j_connection (name) + @neo4j_connection_name = name + end + def neoid_config @neoid_config ||= Neoid::ModelConfig.new(self) end @@ -10,7 +14,7 @@ def neoid_config def neoidable(options = {}) # defaults neoid_config.auto_index = true - neoid_config.enable_model_index = true # but the Neoid.enable_per_model_indexes is false by default. all models will be true only if the primary option is turned on. + neoid_config.enable_model_index = true # but the Neoid::Instance.enable_per_model_indexes is false by default. all models will be true only if the primary option is turned on. yield(neoid_config) if block_given? @@ -21,7 +25,7 @@ def neoidable(options = {}) end def neo_model_index_name - raise "Per Model index is not enabled. Nodes/Relationships are auto indexed with node_auto_index/relationship_auto_index" unless Neoid.config.enable_per_model_indexes || neoid_config.enable_model_index + raise "Per Model index is not enabled. Nodes/Relationships are auto indexed with node_auto_index/relationship_auto_index" unless self.neo4j_connection.config.enable_per_model_indexes || neoid_config.enable_model_index @index_name ||= "#{self.name.tableize}_index" end end diff --git a/lib/neoid/node.rb b/lib/neoid/node.rb index 851e9d1..27d20dd 100644 --- a/lib/neoid/node.rb +++ b/lib/neoid/node.rb @@ -2,7 +2,7 @@ module Neoid module Node def self.from_hash(hash) node = Neography::Node.new(hash) - node.neo_server = Neoid.db + node.neo_server = self.neo4j_connection.db node end @@ -21,8 +21,15 @@ def delete_command :delete_node end + def neo4j_connection + @neorj_connection ||= begin + instance = Neoid::Connections.connection(@neo4j_connection_name) + instance.node_models << self + end + end + def neo_model_index - return nil unless Neoid.config.enable_per_model_indexes + return nil unless self.neo4j_connection.config.enable_per_model_indexes Neoid::logger.info "Node#neo_model_index #{neo_subref_rel_type}" @@ -30,15 +37,15 @@ def neo_model_index g.createManualIndex(neo_model_index_name, Vertex.class); GREMLIN - # Neoid.logger.info "subref query:\n#{gremlin_query}" + # self.neo4j_connection.logger.info "subref query:\n#{gremlin_query}" script_vars = { neo_model_index_name: neo_model_index_name } - Neoid.execute_script_or_add_to_batch gremlin_query, script_vars + self.neo4j_connection.execute_script_or_add_to_batch gremlin_query, script_vars end def neo_subref_node - return nil unless Neoid.config.enable_subrefs + return nil unless self.neo4j_connection.config.enable_subrefs @neo_subref_node ||= begin Neoid::logger.info "Node#neo_subref_node #{neo_subref_rel_type}" @@ -56,14 +63,14 @@ def neo_subref_node subref GREMLIN - # Neoid.logger.info "subref query:\n#{gremlin_query}" + # self.neo4j_connection.logger.info "subref query:\n#{gremlin_query}" script_vars = { neo_subref_rel_type: neo_subref_rel_type, name: self.name } - Neoid.execute_script_or_add_to_batch gremlin_query, script_vars do |value| + self.neo4j_connection.execute_script_or_add_to_batch gremlin_query, script_vars do |value| Neoid::Node.from_hash(value) end end @@ -74,19 +81,19 @@ def reset_neo_subref_node end def neo_search(term, options = {}) - Neoid.search(self, term, options) + self.neo4j_connection.search(self, term, options) end end module InstanceMethods def neo_find_by_id # Neoid::logger.info "Node#neo_find_by_id #{self.class.neo_index_name} #{self.id}" - node = Neoid.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, self.neo_unique_id) + node = self.class.neo4j_connection.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, self.neo_unique_id) node.present? ? Neoid::Node.from_hash(node[0]) : nil end def _neo_save - return unless Neoid.enabled? + return unless self.class.neo4j_connection.enabled? data = self.to_neo.merge(ar_type: self.class.name, ar_id: self.id, Neoid::UNIQUE_ID_KEY => self.neo_unique_id) data.reject! { |k, v| v.nil? } @@ -118,18 +125,18 @@ def _neo_save unique_id_key: Neoid::UNIQUE_ID_KEY, node_data: data, unique_id: self.neo_unique_id, - enable_subrefs: Neoid.config.enable_subrefs, - enable_model_index: Neoid.config.enable_per_model_indexes && self.class.neoid_config.enable_model_index + enable_subrefs: self.class.neo4j_connection.config.enable_subrefs, + enable_model_index: self.class.neo4j_connection.config.enable_per_model_indexes && self.class.neoid_config.enable_model_index } - if Neoid.config.enable_subrefs + if self.class.neo4j_connection.config.enable_subrefs script_vars.update( subref_id: self.class.neo_subref_node.neo_id, neo_subref_node_rel_type: self.class.neo_subref_node_rel_type ) end - if Neoid.config.enable_per_model_indexes && self.class.neoid_config.enable_model_index + if self.class.neo4j_connection.config.enable_per_model_indexes && self.class.neoid_config.enable_model_index script_vars.update( neo_model_index_name: self.class.neo_model_index_name ) @@ -137,7 +144,7 @@ def _neo_save Neoid::logger.info "Node#neo_save #{self.class.name} #{self.id}" - node = Neoid.execute_script_or_add_to_batch(gremlin_query, script_vars) do |value| + node = self.class.neo4j_connection.execute_script_or_add_to_batch(gremlin_query, script_vars) do |value| @_neo_representation = Neoid::Node.from_hash(value) end.then do |result| neo_search_index @@ -152,16 +159,16 @@ def neo_search_index self.class.neoid_config.search_options.fulltext_fields.blank? ) - Neoid.ensure_default_fulltext_search_index + self.class.neo4j_connection.ensure_default_fulltext_search_index - Neoid.db.add_node_to_index(DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, 'ar_type', self.class.name, neo_node.neo_id) + self.class.neo4j_connection.db.add_node_to_index(Neoid::DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, 'ar_type', self.class.name, neo_node.neo_id) self.class.neoid_config.search_options.fulltext_fields.each do |field, options| - Neoid.db.add_node_to_index(DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, "#{field}_fulltext", neo_helper_get_field_value(field, options), neo_node.neo_id) + self.class.neo4j_connection.db.add_node_to_index(Neoid::DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, "#{field}_fulltext", neo_helper_get_field_value(field, options), neo_node.neo_id) end self.class.neoid_config.search_options.index_fields.each do |field, options| - Neoid.db.add_node_to_index(DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, field, neo_helper_get_field_value(field, options), neo_node.neo_id) + self.class.neo4j_connection.db.add_node_to_index(Neoid::DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, field, neo_helper_get_field_value(field, options), neo_node.neo_id) end neo_node @@ -204,7 +211,6 @@ def self.included(receiver) receiver.send :include, Neoid::ModelAdditions receiver.extend ClassMethods receiver.send :include, InstanceMethods - Neoid.node_models << receiver end end end diff --git a/lib/neoid/railtie.rb b/lib/neoid/railtie.rb index fee49a5..a8c9d50 100644 --- a/lib/neoid/railtie.rb +++ b/lib/neoid/railtie.rb @@ -4,7 +4,7 @@ module Neoid class Railtie < Rails::Railtie initializer "neoid.configure_rails_initialization" do config.after_initialize do - Neoid.initialize_all + Neoid::Connections.initialize_all end end diff --git a/lib/neoid/relationship.rb b/lib/neoid/relationship.rb index e91f894..ed35658 100644 --- a/lib/neoid/relationship.rb +++ b/lib/neoid/relationship.rb @@ -3,17 +3,22 @@ module Relationship # this is a proxy that delays loading of start_node and end_node from Neo4j until accessed. # the original Neography Relatioship loaded them on initialization class RelationshipLazyProxy < ::Neography::Relationship + def initialize(hash, instance) + @instance = instance + super(hash) + end + def start_node - @start_node_from_db ||= @start_node = Neography::Node.load(@start_node, Neoid.db) + @start_node_from_db ||= @start_node = Neography::Node.load(@start_node, @instance.db) end def end_node - @end_node_from_db ||= @end_node = Neography::Node.load(@end_node, Neoid.db) + @end_node_from_db ||= @end_node = Neography::Node.load(@end_node, @instance.db) end end def self.from_hash(hash) - relationship = RelationshipLazyProxy.new(hash) + relationship = RelationshipLazyProxy.new(hash, self.neo4j_connection) relationship end @@ -23,17 +28,25 @@ module ClassMethods def delete_command :delete_relationship end + + def neo4j_connection + @neorj_connection ||= begin + instance = Neoid::Connections.connection(@neo4j_connection_name) + initialize_relationship receiver if instance.env_loaded + instance.relationship_models << self + end + end end module InstanceMethods def neo_find_by_id - results = Neoid.db.get_relationship_auto_index(Neoid::UNIQUE_ID_KEY, self.neo_unique_id) + results = self.class.neo4j_connection.db.get_relationship_auto_index(Neoid::UNIQUE_ID_KEY, self.neo_unique_id) relationship = results.present? ? Neoid::Relationship.from_hash(results[0]) : nil relationship end def _neo_save - return unless Neoid.enabled? + return unless self.class.neo4j_connection.enabled? options = self.class.neoid_config.relationship_options @@ -86,11 +99,10 @@ def _neo_save Neoid::logger.info "Relationship#neo_save #{self.class.name} #{self.id}" - relationship = Neoid.execute_script_or_add_to_batch gremlin_query, script_vars do |value| + self.class.neo4j_connection.execute_script_or_add_to_batch gremlin_query, script_vars do |value| Neoid::Relationship.from_hash(value) end - relationship end def neo_load(hash) @@ -106,10 +118,6 @@ def self.included(receiver) receiver.send :include, Neoid::ModelAdditions receiver.send :include, InstanceMethods receiver.extend ClassMethods - - initialize_relationship receiver if Neoid.env_loaded - - Neoid.relationship_models << receiver end def self.meta_data diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0605e36..5764c1e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,7 +19,7 @@ end end -Neoid.db = $neo +Neoid.add_connection(:main, $neo) logger, ActiveRecord::Base.logger = ActiveRecord::Base.logger, Logger.new('/dev/null') ActiveRecord::Base.configurations = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'support/database.yml'))) @@ -37,9 +37,11 @@ end config.before(:each) do - Neoid.node_models.each(&:destroy_all) - Neoid.clean_db(:yes_i_am_sure) - Neoid.reset_cached_variables + Neoid.connections.each do |name, conn| + conn.node_models.each(&:destroy_all) + conn.clean_db(:yes_i_am_sure) + conn.reset_cached_variables + end end end From 6622c52490b4437a8eb0db78384ecb61ff53a978 Mon Sep 17 00:00:00 2001 From: Alex Willemsma Date: Mon, 6 Jan 2014 21:34:14 -1000 Subject: [PATCH 2/6] Some work on hacking up this damn gem to actually work. Seems the gremlin plugin for neo4j is borked, or doesn't like 2.0, or something. Anyway, this thing needs the rest of it's gremlin scripts replaced, which means I have to learn cypher. That's a project for tomorrow. --- lib/neoid.rb | 2 +- lib/neoid/database_cleaner.rb | 10 ++++------ lib/neoid/model_additions.rb | 2 +- lib/neoid/node.rb | 15 ++++++++------- lib/neoid/railtie.rb | 2 +- lib/neoid/relationship.rb | 3 ++- spec/spec_helper.rb | 3 ++- spec/support/models.rb | 1 + 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/neoid.rb b/lib/neoid.rb index ef7d731..076028d 100644 --- a/lib/neoid.rb +++ b/lib/neoid.rb @@ -29,7 +29,7 @@ def connection(name = nil) if !name || name == :default name = default_connection_name if !name && connections.size == 1 - return connections.first + return connections.values.first end end diff --git a/lib/neoid/database_cleaner.rb b/lib/neoid/database_cleaner.rb index 228bc76..2dcdf4a 100644 --- a/lib/neoid/database_cleaner.rb +++ b/lib/neoid/database_cleaner.rb @@ -1,12 +1,10 @@ module Neoid class NeoDatabaseCleaner def self.clean_db(instance, start_node = nil) - puts instance.inspect - start_node ||= instance.db.get_root - instance.db.execute_script <<-GREMLIN - g.V.toList().each { if (it.id != 0) g.removeVertex(it) } - g.indices.each { g.dropIndex(it.indexName); } - GREMLIN + #start_node ||= instance.db.get_root + instance.db.execute_query <<-CYPHER + START n0=node(0),nx=node(*) OPTIONAL MATCH n0-[r0]-(),nx-[rx]-() WHERE nx <> n0 DELETE r0,rx,nx + CYPHER true end diff --git a/lib/neoid/model_additions.rb b/lib/neoid/model_additions.rb index d27df8b..979c485 100644 --- a/lib/neoid/model_additions.rb +++ b/lib/neoid/model_additions.rb @@ -72,7 +72,7 @@ def neo_destroy begin neo_representation.del - rescue Neography::NodeNotFoundException => e + rescue Neography::NodeNotFoundException Neoid::logger.info "Neoid#neo_destroy entity not found #{self.class.name} #{self.id}" end diff --git a/lib/neoid/node.rb b/lib/neoid/node.rb index 27d20dd..3c6d3d7 100644 --- a/lib/neoid/node.rb +++ b/lib/neoid/node.rb @@ -22,16 +22,17 @@ def delete_command end def neo4j_connection - @neorj_connection ||= begin - instance = Neoid::Connections.connection(@neo4j_connection_name) + @neo4j_connection ||= begin + instance = Neoid.connection(@neo4j_connection_name) instance.node_models << self + instance end end def neo_model_index return nil unless self.neo4j_connection.config.enable_per_model_indexes - Neoid::logger.info "Node#neo_model_index #{neo_subref_rel_type}" + self.neo4j_connection.logger.info "Node#neo_model_index #{neo_subref_rel_type}" gremlin_query = <<-GREMLIN g.createManualIndex(neo_model_index_name, Vertex.class); @@ -48,7 +49,7 @@ def neo_subref_node return nil unless self.neo4j_connection.config.enable_subrefs @neo_subref_node ||= begin - Neoid::logger.info "Node#neo_subref_node #{neo_subref_rel_type}" + self.neo4j_connection.logger.info "Node#neo_subref_node #{neo_subref_rel_type}" gremlin_query = <<-GREMLIN q = g.v(0).out(neo_subref_rel_type); @@ -87,7 +88,7 @@ def neo_search(term, options = {}) module InstanceMethods def neo_find_by_id - # Neoid::logger.info "Node#neo_find_by_id #{self.class.neo_index_name} #{self.id}" + # self.neo4j_connection.logger.info "Node#neo_find_by_id #{self.class.neo_index_name} #{self.id}" node = self.class.neo4j_connection.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, self.neo_unique_id) node.present? ? Neoid::Node.from_hash(node[0]) : nil end @@ -142,7 +143,7 @@ def _neo_save ) end - Neoid::logger.info "Node#neo_save #{self.class.name} #{self.id}" + self.neo4j_connection.logger.info "Node#neo_save #{self.class.name} #{self.id}" node = self.class.neo4j_connection.execute_script_or_add_to_batch(gremlin_query, script_vars) do |value| @_neo_representation = Neoid::Node.from_hash(value) @@ -202,7 +203,7 @@ def neo_before_relationship_through_remove(record) end def neo_after_relationship_through_remove(record) - @__neo_temp_rels.each { |record, relationship| relationship.neo_destroy } + @__neo_temp_rels.each { |r, relationship| relationship.neo_destroy } @__neo_temp_rels.delete(record) end end diff --git a/lib/neoid/railtie.rb b/lib/neoid/railtie.rb index a8c9d50..fee49a5 100644 --- a/lib/neoid/railtie.rb +++ b/lib/neoid/railtie.rb @@ -4,7 +4,7 @@ module Neoid class Railtie < Rails::Railtie initializer "neoid.configure_rails_initialization" do config.after_initialize do - Neoid::Connections.initialize_all + Neoid.initialize_all end end diff --git a/lib/neoid/relationship.rb b/lib/neoid/relationship.rb index ed35658..58f49e2 100644 --- a/lib/neoid/relationship.rb +++ b/lib/neoid/relationship.rb @@ -31,9 +31,10 @@ def delete_command def neo4j_connection @neorj_connection ||= begin - instance = Neoid::Connections.connection(@neo4j_connection_name) + instance = Neoid.connection(@neo4j_connection_name) initialize_relationship receiver if instance.env_loaded instance.relationship_models << self + instance end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5764c1e..2482f27 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,7 +6,6 @@ # ENV['NEOID_LOG'] = 'true' uri = URI.parse(ENV["NEO4J_URL"] ? ENV["NEO4J_URL"] : ENV['TRAVIS'] ? "http://localhost:7474" : "http://localhost:7574") -$neo = Neography::Rest.new(uri.to_s) Neography.configure do |c| c.server = uri.host @@ -19,6 +18,8 @@ end end +$neo = Neography::Rest.new(uri.to_s) + Neoid.add_connection(:main, $neo) logger, ActiveRecord::Base.logger = ActiveRecord::Base.logger, Logger.new('/dev/null') diff --git a/spec/support/models.rb b/spec/support/models.rb index 800c84f..a8a0504 100644 --- a/spec/support/models.rb +++ b/spec/support/models.rb @@ -1,5 +1,6 @@ class User < ActiveRecord::Base include ActiveModel::Validations::Callbacks + has_many :likes has_many :movies, through: :likes From e4bcb2c49b278a8a96caa147ea8b27fd8a52e85f Mon Sep 17 00:00:00 2001 From: Alex Willemsma Date: Tue, 7 Jan 2014 13:27:03 -1000 Subject: [PATCH 3/6] Reverting db cleaner script from cypher back to Gremlin. I realized that gremlin (and more) was borked because neoid doesn't support neo4j 2.0 yet, and I was trying to run it against that. Downgrading for now as we want proper support. --- lib/neoid/database_cleaner.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/neoid/database_cleaner.rb b/lib/neoid/database_cleaner.rb index 2dcdf4a..890ef5e 100644 --- a/lib/neoid/database_cleaner.rb +++ b/lib/neoid/database_cleaner.rb @@ -1,10 +1,11 @@ module Neoid class NeoDatabaseCleaner def self.clean_db(instance, start_node = nil) - #start_node ||= instance.db.get_root - instance.db.execute_query <<-CYPHER - START n0=node(0),nx=node(*) OPTIONAL MATCH n0-[r0]-(),nx-[rx]-() WHERE nx <> n0 DELETE r0,rx,nx - CYPHER + start_node ||= instance.db.get_root + instance.db.execute_script <<-GREMLIN + g.V.toList().each { if (it.id != 0) g.removeVertex(it) } + g.indices.each { g.dropIndex(it.indexName); } + GREMLIN true end From d16b6f272e818a73afc48ceed801730dec055dfb Mon Sep 17 00:00:00 2001 From: Alex Willemsma Date: Tue, 7 Jan 2014 14:30:00 -1000 Subject: [PATCH 4/6] Fixed nearly all of the existing unit tests. --- lib/neoid/batch.rb | 2 +- lib/neoid/node.rb | 14 +++++++------- lib/neoid/relationship.rb | 14 +++++++------- spec/neoid/batch_spec.rb | 24 ++++++++++++------------ spec/neoid/config_spec.rb | 4 ++-- spec/neoid/node_spec.rb | 32 ++++++++++++++++---------------- spec/neoid/search_spec.rb | 26 +++++++++++++------------- spec/neoid_spec.rb | 4 ++-- 8 files changed, 60 insertions(+), 60 deletions(-) diff --git a/lib/neoid/batch.rb b/lib/neoid/batch.rb index 9670246..c60c160 100644 --- a/lib/neoid/batch.rb +++ b/lib/neoid/batch.rb @@ -111,7 +111,7 @@ def process_results(results) else return result end - type.from_hash(result) + type.from_hash(result, @instance) end end end diff --git a/lib/neoid/node.rb b/lib/neoid/node.rb index 3c6d3d7..84dd348 100644 --- a/lib/neoid/node.rb +++ b/lib/neoid/node.rb @@ -1,8 +1,8 @@ module Neoid module Node - def self.from_hash(hash) + def self.from_hash(hash, connection) node = Neography::Node.new(hash) - node.neo_server = self.neo4j_connection.db + node.neo_server = connection.db node end @@ -72,7 +72,7 @@ def neo_subref_node } self.neo4j_connection.execute_script_or_add_to_batch gremlin_query, script_vars do |value| - Neoid::Node.from_hash(value) + Neoid::Node.from_hash(value, self.neo4j_connection) end end end @@ -90,7 +90,7 @@ module InstanceMethods def neo_find_by_id # self.neo4j_connection.logger.info "Node#neo_find_by_id #{self.class.neo_index_name} #{self.id}" node = self.class.neo4j_connection.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, self.neo_unique_id) - node.present? ? Neoid::Node.from_hash(node[0]) : nil + node.present? ? Neoid::Node.from_hash(node[0], self.class.neo4j_connection) : nil end def _neo_save @@ -143,10 +143,10 @@ def _neo_save ) end - self.neo4j_connection.logger.info "Node#neo_save #{self.class.name} #{self.id}" + self.class.neo4j_connection.logger.info "Node#neo_save #{self.class.name} #{self.id}" node = self.class.neo4j_connection.execute_script_or_add_to_batch(gremlin_query, script_vars) do |value| - @_neo_representation = Neoid::Node.from_hash(value) + @_neo_representation = Neoid::Node.from_hash(value, self.class.neo4j_connection) end.then do |result| neo_search_index end @@ -184,7 +184,7 @@ def neo_helper_get_field_value(field, options = {}) end def neo_load(hash) - Neoid::Node.from_hash(hash) + Neoid::Node.from_hash(hash, self.class.neo4j_connection) end def neo_node diff --git a/lib/neoid/relationship.rb b/lib/neoid/relationship.rb index 58f49e2..1c70211 100644 --- a/lib/neoid/relationship.rb +++ b/lib/neoid/relationship.rb @@ -17,8 +17,8 @@ def end_node end end - def self.from_hash(hash) - relationship = RelationshipLazyProxy.new(hash, self.neo4j_connection) + def self.from_hash(hash, connection) + relationship = RelationshipLazyProxy.new(hash, connection) relationship end @@ -32,7 +32,7 @@ def delete_command def neo4j_connection @neorj_connection ||= begin instance = Neoid.connection(@neo4j_connection_name) - initialize_relationship receiver if instance.env_loaded + Neoid::Relationship.initialize_relationship self if instance.env_loaded instance.relationship_models << self instance end @@ -42,7 +42,7 @@ def neo4j_connection module InstanceMethods def neo_find_by_id results = self.class.neo4j_connection.db.get_relationship_auto_index(Neoid::UNIQUE_ID_KEY, self.neo_unique_id) - relationship = results.present? ? Neoid::Relationship.from_hash(results[0]) : nil + relationship = results.present? ? Neoid::Relationship.from_hash(results[0], self.class.neo4j_connection) : nil relationship end @@ -98,16 +98,16 @@ def _neo_save rel_type: rel_type } - Neoid::logger.info "Relationship#neo_save #{self.class.name} #{self.id}" + self.class.neo4j_connection.logger.info "Relationship#neo_save #{self.class.name} #{self.id}" self.class.neo4j_connection.execute_script_or_add_to_batch gremlin_query, script_vars do |value| - Neoid::Relationship.from_hash(value) + Neoid::Relationship.from_hash(value, self.class.neo4j_connection) end end def neo_load(hash) - Neoid::Relationship.from_hash(hash) + Neoid::Relationship.from_hash(hash, self.class.neo4j_connection) end def neo_relationship diff --git a/spec/neoid/batch_spec.rb b/spec/neoid/batch_spec.rb index 37cfa2b..70683d1 100644 --- a/spec/neoid/batch_spec.rb +++ b/spec/neoid/batch_spec.rb @@ -3,7 +3,7 @@ describe Neoid::ModelAdditions do context "promises" do it "should run scripts in a batch and return results" do - Neoid.batch do |batch| + Neoid.connection.batch do |batch| batch << [:execute_script, "1"] batch << [:execute_script, "2"] end.then do |results| @@ -12,7 +12,7 @@ end it "should run scripts in a batch with batch_size and flush batch when it's full" do - Neoid.batch(batch_size: 3) do |batch| + Neoid.connection.batch(batch_size: 3) do |batch| (0...9).each do |i| batch.count.should == i % 3 batch << [:execute_script, i.to_s] @@ -24,7 +24,7 @@ end it "should run scripts in a batch with batch_size and return all results" do - Neoid.batch(batch_size: 2) do |batch| + Neoid.connection.batch(batch_size: 2) do |batch| (1..6).each do |i| batch << [:execute_script, i.to_s] end @@ -34,11 +34,11 @@ end it "should return results then process them" do - node_1 = Neoid.db.create_node - node_2 = Neoid.db.create_node - rel = Neoid.db.create_relationship(:related, node_1, node_2) + node_1 = Neoid.connection.db.create_node + node_2 = Neoid.connection.db.create_node + rel = Neoid.connection.db.create_relationship(:related, node_1, node_2) - Neoid.batch do |batch| + Neoid.connection.batch do |batch| batch << [:execute_script, "g.v(neo_id)", neo_id: node_1['self'].split('/').last.to_i] batch << [:execute_script, "g.v(neo_id)", neo_id: node_2['self'].split('/').last] batch << [:execute_script, "g.e(neo_id)", neo_id: rel['self'].split('/').last] @@ -52,7 +52,7 @@ it "should remember what to do after each script has executed, and perform it when batch is flushed" do then_results = [] - Neoid.batch do |batch| + Neoid.connection.batch do |batch| (batch << [:execute_script, "1"]).then { |res| then_results << res } (batch << [:execute_script, "2"]).then { |res| then_results << res } batch << [:execute_script, "3"] @@ -68,7 +68,7 @@ it "should not execute until batch is done" do u1 = u2 = nil - res = Neoid.batch do + res = Neoid.connection.batch do u1 = User.create!(name: "U1") u2 = User.create!(name: "U2") @@ -86,7 +86,7 @@ u1 = User.create!(name: "U1") u2 = User.create!(name: "U2") - res = Neoid.batch do + res = Neoid.connection.batch do u1.name = "U1 update" u2.name = "U2 update" @@ -136,7 +136,7 @@ user movie - res = Neoid.batch do |batch| + res = Neoid.connection.batch do |batch| user.like! movie user.likes.last.neo_find_by_id.should be_nil @@ -156,7 +156,7 @@ user.neo_destroy movie.neo_destroy - res = Neoid.batch do |batch| + res = Neoid.connection.batch do |batch| user.like! movie user.likes.last.neo_find_by_id.should be_nil diff --git a/spec/neoid/config_spec.rb b/spec/neoid/config_spec.rb index 01adc26..215241d 100644 --- a/spec/neoid/config_spec.rb +++ b/spec/neoid/config_spec.rb @@ -3,11 +3,11 @@ describe Neoid::Config do context "config" do it "should store and read config" do - Neoid.configure do |config| + Neoid.connection.configure do |config| config.enable_subrefs = false end - Neoid.config.enable_subrefs.should == false + Neoid.connection.config.enable_subrefs.should == false end end end diff --git a/spec/neoid/node_spec.rb b/spec/neoid/node_spec.rb index c102397..411575a 100644 --- a/spec/neoid/node_spec.rb +++ b/spec/neoid/node_spec.rb @@ -67,64 +67,64 @@ context "subrefs" do it "should connect subrefs to reference node" do - old, Neoid.config.enable_subrefs = Neoid.config.enable_subrefs, true + old, Neoid.connection.config.enable_subrefs = Neoid.connection.config.enable_subrefs, true - Neoid.send(:initialize_subrefs) + Neoid.connection.send(:initialize_subrefs) begin - Neoid.ref_node.rel(:outgoing, :users_subref).should_not be_nil + Neoid.connection.ref_node.rel(:outgoing, :users_subref).should_not be_nil ensure - Neoid.config.enable_subrefs = old + Neoid.connection.config.enable_subrefs = old end end it "should create a relationship with a subref node" do - old, Neoid.config.enable_subrefs = Neoid.config.enable_subrefs, true + old, Neoid.connection.config.enable_subrefs = Neoid.connection.config.enable_subrefs, true - Neoid.send(:initialize_subrefs) + Neoid.connection.send(:initialize_subrefs) begin user = User.create!(name: "Elad") user.neo_node.rel(:incoming, :users).should_not be_nil ensure - Neoid.config.enable_subrefs = old + Neoid.connection.config.enable_subrefs = old end end it "should not create a relationship with a subref node if disabled" do - old, Neoid.config.enable_subrefs = Neoid.config.enable_subrefs, false + old, Neoid.connection.config.enable_subrefs = Neoid.connection.config.enable_subrefs, false begin user = User.create!(name: "Elad") user.neo_node.rel(:incoming, :users_subref).should be_nil ensure - Neoid.config.enable_subrefs = old + Neoid.connection.config.enable_subrefs = old end end end context "per_model_indexes" do it "should create a relationship with a subref node" do - old, Neoid.config.enable_per_model_indexes = Neoid.config.enable_per_model_indexes, true + old, Neoid.connection.config.enable_per_model_indexes = Neoid.connection.config.enable_per_model_indexes, true - Neoid.send(:initialize_per_model_indexes) + Neoid.connection.send(:initialize_per_model_indexes) begin user = User.create!(name: "Elad") - Neoid.db.get_node_index(User.neo_model_index_name, 'ar_id', user.id).should_not be_nil + Neoid.connection.db.get_node_index(User.neo_model_index_name, 'ar_id', user.id).should_not be_nil ensure - Neoid.config.enable_per_model_indexes = old + Neoid.connection.config.enable_per_model_indexes = old end end it "should not create a relationship with a subref node if disabled" do - old, Neoid.config.enable_per_model_indexes = Neoid.config.enable_per_model_indexes, false + old, Neoid.connection.config.enable_per_model_indexes = Neoid.connection.config.enable_per_model_indexes, false begin user = User.create!(name: "Elad") - expect { Neoid.db.get_node_index(User.neo_model_index_name, 'ar_id', user.id) }.to raise_error(Neography::NotFoundException) + expect { Neoid.connection.db.get_node_index(User.neo_model_index_name, 'ar_id', user.id) }.to raise_error(Neography::NotFoundException) ensure - Neoid.config.enable_per_model_indexes = old + Neoid.connection.config.enable_per_model_indexes = old end end end diff --git a/spec/neoid/search_spec.rb b/spec/neoid/search_spec.rb index 40da701..28046bf 100644 --- a/spec/neoid/search_spec.rb +++ b/spec/neoid/search_spec.rb @@ -5,20 +5,20 @@ let(:index_name) { "articles_search_index_#{Time.now.to_f.to_s.gsub('.', '')}" } it "should index and find node in fulltext" do - Neoid.db.create_node_index(index_name, "fulltext", "lucene") + Neoid.connection.db.create_node_index(index_name, "fulltext", "lucene") n = Neography::Node.create(name: "test hello world", year: 2012) - Neoid.db.add_node_to_index(index_name, "name", n.name, n) - Neoid.db.add_node_to_index(index_name, "year", n.year, n) + Neoid.connection.db.add_node_to_index(index_name, "name", n.name, n) + Neoid.connection.db.add_node_to_index(index_name, "year", n.year, n) [ "name:test", "year:2012", "name:test AND year:2012" ].each { |q| - results = Neoid.db.find_node_index(index_name, q) + results = Neoid.connection.db.find_node_index(index_name, q) results.length.should == 1 - Neoid.db.send(:get_id, results).should == n.neo_id + Neoid.connection.db.send(:get_id, results).should == n.neo_id } end @@ -31,11 +31,11 @@ "year:#{r}", "title:#{r} AND year:#{r}" ].each do |q| - results = Neoid.db.find_node_index(Neoid::DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, q) + results = Neoid.connection.db.find_node_index(Neoid::DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, q) results.should_not be_nil results.length.should == 1 - Neoid.db.send(:get_id, results).should == article.neo_node.neo_id + Neoid.connection.db.send(:get_id, results).should == article.neo_node.neo_id end end @@ -80,11 +80,11 @@ end it "should search in multiple types" do - Neoid.search([Article, Movie], "manga").results.should =~ @articles + @movies + Neoid.connection.search([Article, Movie], "manga").results.should =~ @articles + @movies end it "should search in single type when specified" do - Neoid.search([Article], "manga").results.should =~ @articles + Neoid.connection.search([Article], "manga").results.should =~ @articles end end @@ -99,17 +99,17 @@ end it "should return search results only matches with AND" do - Neoid.search([Article],"manga comics").results.size.should eq(1) + Neoid.connection.search([Article],"manga comics").results.size.should eq(1) - Neoid.search([Article],"manga comics",{match_type: "AND"}).results.size.should eq(1) + Neoid.connection.search([Article],"manga comics",{match_type: "AND"}).results.size.should eq(1) end it "should return search results all results with OR" do - Neoid.search([Article],"manga comics",{match_type: "OR"}).results.size.should eq(4) + Neoid.connection.search([Article],"manga comics",{match_type: "OR"}).results.size.should eq(4) end it "should fail with wrong match_type" do - expect {Neoid.search([Article],"manga comics",{match_type: "MAYBE"})}.to raise_error("Invalid match_type option. Valid values are AND,OR") + expect {Neoid.connection.search([Article],"manga comics",{match_type: "MAYBE"})}.to raise_error("Invalid match_type option. Valid values are AND,OR") end end diff --git a/spec/neoid_spec.rb b/spec/neoid_spec.rb index 40d49b0..48ffc1b 100644 --- a/spec/neoid_spec.rb +++ b/spec/neoid_spec.rb @@ -3,9 +3,9 @@ describe Neoid do context "subrefs" do it "should create all subrefs on initialization" do - Neoid.node_models.each do |klass| + Neoid.connection.node_models.each do |klass| klass.instance_variable_get(:@neo_subref_node).should_not be_nil end end end -end \ No newline at end of file +end From 02659014a04c63ffb55f9497da160ff4b1144edc Mon Sep 17 00:00:00 2001 From: Alex Willemsma Date: Tue, 7 Jan 2014 17:06:02 -1000 Subject: [PATCH 5/6] Fixed some issues with initializations to get all tests passing, and added a couple tests to cover the new ability to select a connection from the pool. --- lib/neoid.rb | 4 ++-- lib/neoid/model_additions.rb | 4 ++-- lib/neoid/node.rb | 14 ++++++-------- lib/neoid/relationship.rb | 12 +++++------- spec/neoid/instance_spec.rb | 11 +++++++++++ spec/neoid_spec.rb | 17 ++++++++++++----- spec/spec_helper.rb | 1 + spec/support/models.rb | 11 +++++++++-- 8 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 spec/neoid/instance_spec.rb diff --git a/lib/neoid.rb b/lib/neoid.rb index 076028d..e9578b1 100644 --- a/lib/neoid.rb +++ b/lib/neoid.rb @@ -33,10 +33,10 @@ def connection(name = nil) end end + raise ArgumentError.new("No Neo4j connection with name #{name}.") unless name && connections.key?(name) + if connections.key? name connections[name] - else - raise ArgumentError.new("No Neo4j connection with name #{name}.") end end diff --git a/lib/neoid/model_additions.rb b/lib/neoid/model_additions.rb index 979c485..4d6e7cf 100644 --- a/lib/neoid/model_additions.rb +++ b/lib/neoid/model_additions.rb @@ -3,8 +3,8 @@ module ModelAdditions module ClassMethods attr_reader :neoid_config - def use_neo4j_connection (name) - @neo4j_connection_name = name + def neo4j_connection + @neo4j_connection ||= Neoid.connection(@neo4j_connection_name) end def neoid_config diff --git a/lib/neoid/node.rb b/lib/neoid/node.rb index 84dd348..ad4e59b 100644 --- a/lib/neoid/node.rb +++ b/lib/neoid/node.rb @@ -9,6 +9,12 @@ def self.from_hash(hash, connection) module ClassMethods attr_accessor :neo_subref_node + def neo_init (connection_name = nil) + @neo4j_connection_name = connection_name + neo4j_connection.node_models << self + neo4j_connection + end + def neo_subref_rel_type @_neo_subref_rel_type ||= "#{self.name.tableize}_subref" end @@ -21,14 +27,6 @@ def delete_command :delete_node end - def neo4j_connection - @neo4j_connection ||= begin - instance = Neoid.connection(@neo4j_connection_name) - instance.node_models << self - instance - end - end - def neo_model_index return nil unless self.neo4j_connection.config.enable_per_model_indexes diff --git a/lib/neoid/relationship.rb b/lib/neoid/relationship.rb index 1c70211..98490a7 100644 --- a/lib/neoid/relationship.rb +++ b/lib/neoid/relationship.rb @@ -29,13 +29,11 @@ def delete_command :delete_relationship end - def neo4j_connection - @neorj_connection ||= begin - instance = Neoid.connection(@neo4j_connection_name) - Neoid::Relationship.initialize_relationship self if instance.env_loaded - instance.relationship_models << self - instance - end + def neo_init (connection_name = nil) + @neo4j_connection_name = connection_name + Neoid::Relationship.initialize_relationship self if neo4j_connection.env_loaded + neo4j_connection.relationship_models << self + neo4j_connection end end diff --git a/spec/neoid/instance_spec.rb b/spec/neoid/instance_spec.rb new file mode 100644 index 0000000..b7aed49 --- /dev/null +++ b/spec/neoid/instance_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Neoid::Instance do + context "subrefs" do + it "should create all subrefs on initialization" do + Neoid.connection.node_models.each do |klass| + klass.instance_variable_get(:@neo_subref_node).should_not be_nil + end + end + end +end diff --git a/spec/neoid_spec.rb b/spec/neoid_spec.rb index 48ffc1b..ed45d48 100644 --- a/spec/neoid_spec.rb +++ b/spec/neoid_spec.rb @@ -1,11 +1,18 @@ require 'spec_helper' describe Neoid do - context "subrefs" do - it "should create all subrefs on initialization" do - Neoid.connection.node_models.each do |klass| - klass.instance_variable_get(:@neo_subref_node).should_not be_nil - end + context "connection" do + it "should use the default connection when none explicitly set" do + default = Neoid.default_connection_name + default.should be + + Like.neo4j_connection.should eq Neoid.connection(default) + end + + it "should use the given connection when one IS explicitly set" do + # NOTE: The user model (in support/models) is hard-coded to use the + # 'main' connection, for this test. + User.neo4j_connection.should eq Neoid.connection(:main) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2482f27..318b66d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,6 +20,7 @@ $neo = Neography::Rest.new(uri.to_s) +Neoid.default_connection_name = :main Neoid.add_connection(:main, $neo) logger, ActiveRecord::Base.logger = ActiveRecord::Base.logger, Logger.new('/dev/null') diff --git a/spec/support/models.rb b/spec/support/models.rb index a8a0504..00ad2c6 100644 --- a/spec/support/models.rb +++ b/spec/support/models.rb @@ -1,6 +1,5 @@ class User < ActiveRecord::Base include ActiveModel::Validations::Callbacks - has_many :likes has_many :movies, through: :likes @@ -21,7 +20,8 @@ def unlike!(movie) end include Neoid::Node - + neo_init :main + neoidable do |c| c.field :name c.field :slug @@ -35,6 +35,7 @@ class Movie < ActiveRecord::Base has_many :users, through: :likes include Neoid::Node + neo_init neoidable do |c| c.field :name @@ -58,6 +59,7 @@ class UserFollow < ActiveRecord::Base belongs_to :item, polymorphic: true include Neoid::Relationship + neo_init neoidable do |c| c.relationship start_node: :user, end_node: :item, type: :follows @@ -71,6 +73,7 @@ class Like < ActiveRecord::Base belongs_to :movie include Neoid::Relationship + neo_init neoidable do |c| c.relationship start_node: :user, end_node: :movie, type: :likes @@ -81,6 +84,8 @@ class Like < ActiveRecord::Base class Article < ActiveRecord::Base include ActiveModel::Validations::Callbacks include Neoid::Node + neo_init + neoidable do |c| c.field :title c.field :year @@ -101,6 +106,8 @@ class Article < ActiveRecord::Base class NoAutoIndexNode < ActiveRecord::Base include ActiveModel::Validations::Callbacks include Neoid::Node + neo_init + neoidable auto_index: false do |c| c.field :name end From 23d830ee1b47a8804455704d51c353a7445a5669 Mon Sep 17 00:00:00 2001 From: Alex Willemsma Date: Tue, 7 Jan 2014 17:22:11 -1000 Subject: [PATCH 6/6] Minor cleanup and readme updates to reflect changes. --- README.md | 36 +++++++++++++++++++++--------------- lib/neoid.rb | 12 +++++++++--- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 6313d36..9fed499 100644 --- a/README.md +++ b/README.md @@ -58,12 +58,11 @@ Neography.configure do |c| end end -Neoid.db = $neo - -Neoid.configure do |c| +Neoid.add_connection(:main, $neo) do |c| # should Neoid create sub-reference from the ref node (id#0) to every node-model? default: true c.enable_subrefs = true end +Neoid.default_connection_name = :main # Optional if only one connection added (it automagically becomse the default). ``` `01_` in the file name is in order to get this file loaded first, before the models (initializers are loaded alphabetically). @@ -75,12 +74,13 @@ If you have a better idea (I bet you do!) please let me know. #### Nodes -For nodes, first include the `Neoid::Node` module in your model: +For nodes, first include the `Neoid::Node` module in your model, and call the neo_init method: ```ruby class User < ActiveRecord::Base include Neoid::Node + neo_init :main # Connection this model should use. Optional. If not given, uses default connection. end ``` @@ -91,6 +91,7 @@ Then, you can customize what fields will be saved on the node in Neo4j, inside ` ```ruby class User < ActiveRecord::Base include Neoid::Node + neo_init neoidable do |c| c.field :slug @@ -112,6 +113,7 @@ Let's assume that a `User` can `Like` `Movie`s: class User < ActiveRecord::Base include Neoid::Node + neo_init has_many :likes has_many :movies, through: :likes @@ -127,6 +129,7 @@ end class Movie < ActiveRecord::Base include Neoid::Node + neo_init has_many :likes has_many :users, through: :likes @@ -156,6 +159,7 @@ class Like < ActiveRecord::Base belongs_to :movie include Neoid::Relationship + neo_init neoidable do |c| c.relationship start_node: :user, end_node: :movie, type: :likes @@ -188,6 +192,7 @@ If you'd like to save nodes manually rather than after_save, use `auto_index: fa ```ruby class User < ActiveRecord::Base include Neoid::Node + neo_init neoidable auto_index: false do |c| end @@ -211,10 +216,10 @@ Nodes and relationships are auto indexed in the `node_auto_index` and `relations That means, you can query like this: ```ruby -Neoid.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, user.neo_unique_id) +User.neo4j_connection.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, user.neo_unique_id) # => returns a Neography hash -Neoid::Node.from_hash(Neoid.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, user.neo_unique_id)) +Neoid::Node.from_hash(User.neo4j_connection.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, user.neo_unique_id)) # => returns a Neography::Node ``` @@ -223,7 +228,7 @@ Neoid::Node.from_hash(Neoid.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, user.ne If Subreferences are enabled, you can get the subref node and then get all attached nodes: ```ruby -Neoid.ref_node.outgoing('users_subref').first.outgoing('users').to_a +User.neo4j_connection.ref_node.outgoing('users_subref').first.outgoing('users').to_a # => this, according to Neography, returns an array of Neography::Node so no conversion is needed ``` @@ -249,7 +254,7 @@ gremlin_query = <<-GREMLIN m.sort{-it.value}.collect{it.key.ar_id} GREMLIN -movie_ids = Neoid.db.execute_script(gremlin_query) +movie_ids = Movie.neo4j_connection.db.execute_script(gremlin_query) Movie.where(id: movie_ids) ``` @@ -275,7 +280,7 @@ gremlin_query = <<-GREMLIN .except(movies).collect{it.ar_id} GREMLIN -movie_ids = Neoid.db.execute_script(gremlin_query, unique_id_key: Neoid::UNIQUE_ID_KEY, user_unique_id: user.neo_unique_id) +movie_ids = Movie.neo4j_connection.db.execute_script(gremlin_query, unique_id_key: Neoid::UNIQUE_ID_KEY, user_unique_id: user.neo_unique_id) Movie.where(id: movie_ids) ``` @@ -291,6 +296,7 @@ Using `search` block inside a `neoidable` block, you can store certain fields. class Movie < ActiveRecord::Base include Neoid::Node + neo_init neoidable do |c| c.field :slug @@ -319,8 +325,8 @@ Movie.neo_search("*hello*").results # same as above but returns hashes with the values that were indexed on Neo4j Movie.search("*hello*").hits -# search in multiple types -Neoid.neo_search([Movie, User], "hello") +# search in multiple types (within a given neo4j instance) +Neoid.connection(:main).neo_search([Movie, User], "hello") # search with exact matches (pass a hash of field/value) Movie.neo_search(year: 2013).results @@ -335,7 +341,7 @@ Neoid has a batch ability, that is good for mass updateing/inserting of nodes/re A few examples, easy to complex: ```ruby -Neoid.batch(batch_size: 100) do +Neoid.connection(:main).batch(batch_size: 100) do User.all.each(&:neo_save) end ``` @@ -344,7 +350,7 @@ With `then`: ```ruby User.first.name # => "Elad" -Neoid.batch(batch_size: 100) do +Neoid.connection(:main).batch(batch_size: 100) do User.all.each(&:neo_save) end.then do |results| # results is an array of the script results from neo4j REST. @@ -358,7 +364,7 @@ end With individual `then` as well as `then` for the entire batch: ```ruby -Neoid.batch(batch_size: 30) do |batch| +Neoid.connection(:main).batch(batch_size: 30) do |batch| (1..90).each do |i| (batch << [:create_node, { name: "Hello #{i}" }]).then { |result| puts result.name } end @@ -378,7 +384,7 @@ If you have an existing database and just want to integrate Neoid, configure the Use batches! It's free, and much faster. Also, you should use `includes` to incude the relationship edges on relationship entities, so it doesn't query the DB on each relationship. ```ruby -Neoid.batch do +Neoid.connection(:main).batch do [ Like.includes(:user).includes(:movie), OtherRelationshipModel.includes(:from_model).includes(:to_model) ].each { |model| model.all.each(&:neo_save) } NodeModel.all.each(&:neo_save) diff --git a/lib/neoid.rb b/lib/neoid.rb index e9578b1..803e1c9 100644 --- a/lib/neoid.rb +++ b/lib/neoid.rb @@ -47,9 +47,15 @@ def add_connection(name, connection, &block) end def initialize_all - connections.each do |name, instance| - instance.initialize_all - end + connections.values.each(&:initialize_all) + end + + def clean_db(confirm) + connections.values.each {|i| i.clean_db(confirm)} + end + + def reset_cached_variables + connections.values.each(&:reset_cached_variables) end end end