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

  • MARSHAL_INSTANCE_VARIABLES
  • WRAPPED_CLASSES
  • MODELS_FOR_LABELS_CACHE
  • MODELS_TO_RELOAD
  • DATE_KEY_REGEX
  • DEPRECATED_OBJECT_METHODS

Methods

#==

Performs equality checking on the result of attributes and its type.

def ==(other)
  return false unless other.instance_of? self.class
  attributes == other.attributes
end

#[]

def read_attribute(name)
  respond_to?(name) ? send(name) : nil
end
#[]=

Write a single attribute to the model’s attribute hash.

def write_attribute(name, value)
  if respond_to? "#{name}="
    send "#{name}=", value
  else
    fail Neo4j::UnknownAttributeError, "unknown attribute: #{name}"
  end
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
#_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
#assign_attributes

Mass update a model’s attributes

def assign_attributes(new_attributes = nil)
  return unless new_attributes.present?
  new_attributes.each do |name, value|
    writer = :"#{name}="
    send(writer, value) if respond_to?(writer)
  end
end

#association_proxy

def association_proxy(name, options = {})
  name = name.to_sym
  hash = association_proxy_hash(name, options)
  association_proxy_cache_fetch(hash) do
    if result_cache = self.instance_variable_get('@source_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_proxy_hash

def association_proxy_hash(name, options = {})
  [name.to_sym, options.values_at(:node, :rel, :labels, :rel_length)].hash
end

#association_query_proxy

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

Read the raw attribute value

def attribute_before_type_cast(name)
  @attributes ||= {}
  @attributes[name.to_s]
end
#attributes

Returns a Hash of all attributes

def attributes
  attributes_map { |name| send name }
end
#attributes=

Mass update a model’s attributes

def attributes=(new_attributes)
  assign_attributes(new_attributes)
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

#clear_deferred_nodes_for_association

def clear_deferred_nodes_for_association(association_name)
  deferred_nodes_for_association(association_name.to_sym).clear
end
#concurrent_increment!

Increments concurrently a numeric attribute by a centain amount

def concurrent_increment!(attribute, by = 1)
  query_node = Neo4j::Session.query.match_nodes(n: neo_id)
  increment_by_query! query_node, attribute, by
end
#conditional_callback

Allows you to perform a callback if a condition is not satisfied.

def conditional_callback(kind, guard)
  return yield if guard
  run_callbacks(kind) { yield }
end

#declared_properties

def declared_properties
  self.class.declared_properties
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

#defer_create

def defer_create(association_name, object, options = {})
  clear_deferred_nodes_for_association(association_name) if options[:clear]

  deferred_nodes_for_association(association_name) << object
end
#deferred_create_cache

The values in this Hash are returned and used outside by reference so any modifications to the Array should be in-place

def deferred_create_cache
  @deferred_create_cache ||= {}
end

#deferred_nodes_for_association

def deferred_nodes_for_association(association_name)
  deferred_create_cache[association_name.to_sym] ||= []
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
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
#increment

Increments a numeric attribute by a centain amount

def increment(attribute, by = 1)
  self[attribute] ||= 0
  self[attribute] += by
  self
end
#increment!

Convenience method to increment numeric attribute and #save at the same time

def increment!(attribute, by = 1)
  increment(attribute, by).update_attribute(attribute, self[attribute])
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

#init_on_reload

def init_on_reload(reloaded)
  @attributes = nil
  init_on_load(reloaded, reloaded.props)
end

#initialize

def initialize(args = nil)
  symbol_args = args.is_a?(Hash) ? args.symbolize_keys : args
  super(symbol_args)
end

#inject_defaults!

def inject_defaults!(starting_props)
  return starting_props if self.class.declared_properties.declared_property_defaults.empty?
  self.class.declared_properties.inject_defaults!(self, starting_props || {})
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
  attribute_descriptions = inspect_attributes.map do |key, value|
    "#{Neo4j::ANSI::CYAN}#{key}: #{Neo4j::ANSI::CLEAR}#{value.inspect}"
  end.join(', ')

  separator = ' ' unless attribute_descriptions.empty?
  "#<#{Neo4j::ANSI::YELLOW}#{self.class.name}#{Neo4j::ANSI::CLEAR}#{separator}#{attribute_descriptions}>"
end

#labels

def labels
  @_persisted_obj.labels
end

#labels_for_create

def labels_for_create
  self.class.mapped_label_names
end

#marshal_dump

def marshal_dump
  marshal_instance_variables.map(&method(:instance_variable_get))
end

#marshal_load

def marshal_load(array)
  marshal_instance_variables.zip(array).each do |var, value|
    instance_variable_set(var, value)
  end
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_deferred_creations?

def pending_deferred_creations?
  !deferred_create_cache.values.all?(&:empty?)
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 * 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!
  props_with_defaults = inject_defaults!(props)
  converted_props = props_for_db(props_with_defaults)
  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!
  inject_defaults!(changed_props)
  props_for_db(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

def read_attribute(name)
  respond_to?(name) ? send(name) : 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
  reloaded = self.class.load_entity(neo_id)
  reloaded ? init_on_reload(reloaded._persisted_obj) : nil
end

#reload_properties!

def reload_properties!(properties)
  @attributes = nil
  convert_and_assign_attributes(properties)
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)
  save(*args) or fail(RecordInvalidError, self) # rubocop:disable Style/AndOr
end

#send_props

def send_props(hash)
  return hash if hash.blank?
  hash.each { |key, value| 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
#write_attribute

Write a single attribute to the model’s attribute hash.

def write_attribute(name, value)
  if respond_to? "#{name}="
    send "#{name}=", value
  else
    fail Neo4j::UnknownAttributeError, "unknown attribute: #{name}"
  end
end