ActiveNode

Makes Neo4j nodes and relationships behave like ActiveRecord objects. By including this module in your class it will create a mapping for the node to your ruby class by using a Neo4j Label with the same name as the class. When the node is loaded from the database it will check if there is a ruby class for the labels it has. If there Ruby class with the same name as the label then the Neo4j node will be wrapped in a new object of that class.

= ClassMethods * {Neo4j::ActiveNode::Labels::ClassMethods} defines methods like: <tt>index</tt> and <tt>find</tt> * {Neo4j::ActiveNode::Persistence::ClassMethods} defines methods like: <tt>create</tt> and <tt>create!</tt> * {Neo4j::ActiveNode::Property::ClassMethods} defines methods like: <tt>property</tt>.

Constants

  • WRAPPED_CLASSES
  • MODELS_FOR_LABELS_CACHE
  • MODELS_TO_RELOAD
  • USES_CLASSNAME

Methods

#==

def ==(other)
  other.class == self.class && other.id == id
end
#[]

Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr

def read_attribute(name)
  super(name)
rescue ActiveAttr::UnknownAttributeError
  nil
end

#_active_record_destroyed_behavior?

def _active_record_destroyed_behavior?
  fail 'Remove this workaround in 6.0.0' if Neo4j::VERSION >= '6.0.0'

  !!Neo4j::Config[:_active_record_destroyed_behavior]
end
#_create_node

TODO: This does not seem like it should be the responsibility of the node. Creates an unwrapped node in the database.

def _create_node(node_props, labels = labels_for_create)
  self.class.neo4j_session.create_node(node_props, labels)
end
#_destroyed_double_check?

These two methods should be removed in 6.0.0

def _destroyed_double_check?
  if _active_record_destroyed_behavior?
    false
  else
    (!new_record? && !exist?)
  end
end
#_persisted_obj

Returns the value of attribute _persisted_obj

def _persisted_obj
  @_persisted_obj
end

#_rels_delegator

def _rels_delegator
  fail "Can't access relationship on a non persisted node" unless _persisted_obj
  _persisted_obj
end
#add_label

adds one or more labels

def add_label(*label)
  @_persisted_obj.add_label(*label)
end

#apply_default_values

def apply_default_values
  return if self.class.declared_property_defaults.empty?
  self.class.declared_property_defaults.each_pair do |key, value|
    self.send("#{key}=", value) if self.send(key).nil?
  end
end
#as

Starts a new QueryProxy with the starting identifier set to the given argument and QueryProxy source_object set to the node instance. This method does not exist within QueryProxy and can only be used to start a new chain.

def as(node_var)
  self.class.query_proxy(node: node_var, source_object: self).match_to(self)
end

#association_proxy

def association_proxy(name, options = {})
  name = name.to_sym
  hash = [name, options.values_at(:node, :rel, :labels, :rel_length)].hash
  association_proxy_cache_fetch(hash) do
    if result_cache = self.instance_variable_get('@source_query_proxy_result_cache')
      result_by_previous_id = previous_proxy_results_by_previous_id(result_cache, name)

      result_cache.inject(nil) do |proxy_to_return, object|
        proxy = fresh_association_proxy(name, options.merge(start_object: object), result_by_previous_id[object.neo_id])

        object.association_proxy_cache[hash] = proxy

        (self == object ? proxy : proxy_to_return)
      end
    else
      fresh_association_proxy(name, options)
    end
  end
end
#association_proxy_cache

Returns the current AssociationProxy cache for the association cache. It is in the format { :association_name => AssociationProxy} This is so that we * don’t need to re-build the QueryProxy objects * also because the QueryProxy object caches it’s results * so we don’t need to query again * so that we can cache results from association calls or eager loading

def association_proxy_cache
  @association_proxy_cache ||= {}
end

#association_proxy_cache_fetch

def association_proxy_cache_fetch(key)
  association_proxy_cache.fetch(key) do
    value = yield
    association_proxy_cache[key] = value
  end
end

#association_query_proxy

def association_query_proxy(name, options = {})
  self.class.send(:association_query_proxy, name, {start_object: self}.merge!(options))
end

#cache_key

def cache_key
  if self.new_record?
    "#{model_cache_key}/new"
  elsif self.respond_to?(:updated_at) && !self.updated_at.blank?
    "#{model_cache_key}/#{neo_id}-#{self.updated_at.utc.to_s(:number)}"
  else
    "#{model_cache_key}/#{neo_id}"
  end
end
#called_by

Returns the value of attribute called_by

def called_by
  @called_by
end
#called_by=

Sets the attribute called_by

def called_by=(value)
  @called_by = value
end

#declared_property_manager

def declared_property_manager
  self.class.declared_property_manager
end

#default_properties

def default_properties
  @default_properties ||= Hash.new(nil)
end

#default_properties=

def default_properties=(properties)
  @default_property_value = properties[default_property_key]
end

#default_property

def default_property(key)
  return nil unless key == default_property_key
  default_property_value
end

#default_property_key

def default_property_key
  self.class.default_property_key
end
#default_property_value

Returns the value of attribute default_property_value

def default_property_value
  @default_property_value
end

#dependent_children

def dependent_children
  @dependent_children ||= []
end
#destroy
nodoc:
def destroy #:nodoc:
  tx = Neo4j::Transaction.new
  run_callbacks(:destroy) { super }
rescue
  @_deleted = false
  @attributes = @attributes.dup
  tx.mark_failed
  raise
ensure
  tx.close if tx
end
#destroyed?

Returns +true+ if the object was destroyed.

def destroyed?
  @_deleted || _destroyed_double_check?
end

#eql?

def ==(other)
  other.class == self.class && other.id == id
end

#exist?

def exist?
  _persisted_obj && _persisted_obj.exist?
end

#freeze

def freeze
  @attributes.freeze
  self
end

#frozen?

def frozen?
  @attributes.frozen?
end

#hash

def hash
  id.hash
end

#id

def id
  id = neo_id
  id.is_a?(Integer) ? id : nil
end
#init_on_load

called when loading the node from the database

def init_on_load(persisted_node, properties)
  self.class.extract_association_attributes!(properties)
  @_persisted_obj = persisted_node
  changed_attributes && changed_attributes.clear
  @attributes = convert_and_assign_attributes(properties)
end

#initialize

def initialize(args = nil)
  run_callbacks(:initialize) { super }
end
#inject_primary_key!

As the name suggests, this inserts the primary key (id property) into the properties hash. The method called here, default_property_values, is a holdover from an earlier version of the gem. It does NOT contain the default values of properties, it contains the Default Property, which we now refer to as the ID Property. It will be deprecated and renamed in a coming refactor.

def inject_primary_key!(converted_props)
  self.class.default_property_values(self).tap do |destination_props|
    destination_props.merge!(converted_props) if converted_props.is_a?(Hash)
  end
end

#inspect

def inspect
  id_property_name = self.class.id_property_name.to_s
  attribute_pairs = attributes.except(id_property_name).sort.map { |key, value| "#{key}: #{value.inspect}" }
  attribute_pairs.unshift("#{id_property_name}: #{self.send(id_property_name).inspect}")
  attribute_descriptions = attribute_pairs.join(', ')
  separator = ' ' unless attribute_descriptions.empty?
  "#<#{self.class.name}#{separator}#{attribute_descriptions}>"
end

#labels

def labels
  @_persisted_obj.labels
end

#labels_for_create

def labels_for_create
  self.class.mapped_label_names
end

#neo4j_obj

def neo4j_obj
  _persisted_obj || fail('Tried to access native neo4j object on a non persisted object')
end

#neo_id

def neo_id
  _persisted_obj ? _persisted_obj.neo_id : nil
end
#new?

Returns +true+ if the record hasn’t been saved to Neo4j yet.

def new_record?
  !_persisted_obj
end
#new_record?

Returns +true+ if the record hasn’t been saved to Neo4j yet.

def new_record?
  !_persisted_obj
end

#pending_associations

def pending_associations
  @pending_associations ||= {}
end

#pending_associations?

def pending_associations?
  !@pending_associations.blank?
end
#persisted?

Returns +true+ if the record is persisted, i.e. it’s not a new record and it was not destroyed

def persisted?
  !new_record? && !destroyed?
end

#props

def props
  attributes.reject { |_, v| v.nil? }.symbolize_keys
end
#props_for_create

Returns a hash containing: * All properties and values for insertion in the database * A uuid (or equivalent) key and value * A _classname property, if one is to be set * Timestamps, if the class is set to include them. Note that the UUID is added to the hash but is not set on the node. The timestamps, by comparison, are set on the node prior to addition in this hash.

def props_for_create
  inject_timestamps!
  converted_props = props_for_db(props)
  inject_classname!(converted_props)
  inject_defaults!(converted_props)
  return converted_props unless self.class.respond_to?(:default_property_values)
  inject_primary_key!(converted_props)
end

#props_for_persistence

def props_for_persistence
  _persisted_obj ? props_for_update : props_for_create
end

#props_for_update

def props_for_update
  update_magic_properties
  changed_props = attributes.select { |k, _| changed_attributes.include?(k) }
  changed_props.symbolize_keys!
  props_for_db(changed_props)
  inject_defaults!(changed_props)
end
#query_as

Returns a Query object with the current node matched the specified variable name

def query_as(node_var)
  self.class.query_as(node_var, false).where("ID(#{node_var})" => self.neo_id)
end
#read_attribute

Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr

def read_attribute(name)
  super(name)
rescue ActiveAttr::UnknownAttributeError
  nil
end
#read_attribute_for_validation

Implements the ActiveModel::Validation hook method.

def read_attribute_for_validation(key)
  respond_to?(key) ? send(key) : self[key]
end

#reload

def reload
  return self if new_record?
  association_proxy_cache.clear if respond_to?(:association_proxy_cache)
  changed_attributes && changed_attributes.clear
  unless reload_from_database
    @_deleted = true
    freeze
  end
  self
end

#reload_from_database

def reload_from_database
  # TODO: - Neo4j::IdentityMap.remove_node_by_id(neo_id)
  if reloaded = self.class.load_entity(neo_id)
    send(:attributes=, reloaded.attributes)
  end
  reloaded
end
#remove_label

Removes one or more labels Be careful, don’t remove the label representing the Ruby class.

def remove_label(*label)
  @_persisted_obj.remove_label(*label)
end
#save

The validation process on save can be skipped by passing false. The regular Model#save method is replaced with this when the validations module is mixed in, which it is by default.

def save(options = {})
  result = perform_validations(options) ? super : false
  if !result
    Neo4j::Transaction.current.failure if Neo4j::Transaction.current
  end
  result
end
#save!

Persist the object to the database. Validations and Callbacks are included by default but validation can be disabled by passing :validate => false to #save! Creates a new transaction.

def save!(*args)
  fail RecordInvalidError, self unless save(*args)
end

#send_props

def send_props(hash)
  return hash if hash.blank?
  hash.each { |key, value| self.send("#{key}=", value) }
end

#serializable_hash

def serializable_hash(*args)
  super.merge(id: id)
end

#serialized_properties

def serialized_properties
  self.class.serialized_properties
end
#to_key

Returns an Enumerable of all (primary) key attributes or nil if model.persisted? is false

def to_key
  _persisted_obj ? [id] : nil
end
#touch
nodoc:
def touch #:nodoc:
  run_callbacks(:touch) { super }
end
#update

Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved. If saving fails because the resource is invalid then false will be returned.

def update(attributes)
  self.attributes = process_attributes(attributes)
  save
end
#update!

Same as {#update_attributes}, but raises an exception if saving fails.

def update!(attributes)
  self.attributes = process_attributes(attributes)
  save!
end
#update_attribute

Convenience method to set attribute and #save at the same time

def update_attribute(attribute, value)
  send("#{attribute}=", value)
  self.save
end
#update_attribute!

Convenience method to set attribute and #save! at the same time

def update_attribute!(attribute, value)
  send("#{attribute}=", value)
  self.save!
end
#update_attributes

Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved. If saving fails because the resource is invalid then false will be returned.

def update(attributes)
  self.attributes = process_attributes(attributes)
  save
end
#update_attributes!

Same as {#update_attributes}, but raises an exception if saving fails.

def update!(attributes)
  self.attributes = process_attributes(attributes)
  save!
end

#valid?

def valid?(context = nil)
  context ||= (new_record? ? :create : :update)
  super(context)
  errors.empty?
end
#wrapper

Implements the Neo4j::Node#wrapper and Neo4j::Relationship#wrapper method so that we don’t have to care if the node is wrapped or not.

def wrapper
  self
end