module Neo4j
module Rails
# Defines the create, delete and update methods.
# @see ClassMethods class methods when including this module
module Persistence
extend ActiveSupport::Concern
extend TxMethods
# 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.
# @param (see Neo4j::Rails::Validations#save)
# @return [Boolean] true if it was persisted
# @see Neo4j::Rails::Validations Neo4j::Rails::Validations - for the :validate parameter
# @see Neo4j::Rails::Callbacks Neo4j::Rails::Callbacks - for callbacks
def save(*)
create_or_update
end
tx_methods :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.
#
# @raise a RecordInvalidError if there is a problem during save.
# @param (see Neo4j::Rails::Validations#save)
# @return nil
# @see #save
# @see Neo4j::Rails::Validations Neo4j::Rails::Validations - for the :validate parameter
# @see Neo4j::Rails::Callbacks Neo4j::Rails::Callbacks - for callbacks
def save!(*args)
unless save(*args)
raise RecordInvalidError.new(self)
end
end
# Removes the node from Neo4j and freezes the object.
def destroy
delete
freeze
end
# Same as #destroy but doesn't run destroy callbacks and doesn't freeze
# the object. Creates a new transaction
def delete
del unless new_record? || destroyed?
set_deleted_properties
end
tx_methods :delete
# Returns +true+ if the object was destroyed.
def destroyed?
@_deleted || (!new_record? && !self.class.load_entity(neo_id))
end
# 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
# Returns +true+ if the record hasn't been saved to Neo4j yet.
def new_record?
_java_entity.nil?
end
alias :new? :new_record?
# Freeze the properties hash.
def freeze
@_properties.freeze
self
end
# Returns +true+ if the properties hash has been frozen.
def frozen?
reload unless new_record?
@_properties.frozen?
end
module ClassMethods
def transaction(&block)
Neo4j::Rails::Transaction.run do |tx|
block.call(tx)
end
end
def new(*args, &block)
instance = orig_new(*args, &block)
instance.instance_eval(&block) if block
instance
end
# Initialize a model and set a bunch of attributes at the same time. Returns
# the object whether saved successfully or not.
def create(*args)
new(*args).tap do |o|
yield o if block_given?
o.save
end
end
# Get the indexed entity, creating it (exactly once) if no indexed entity exist.
#
# @example Creating a Unique node
#
# class MyNode < Neo4j::Rails::Model
# property :email, :index => :exact, :unique => true
# end
#
# node = MyNode.get_or_create(:email =>'jimmy@gmail.com', :name => 'jimmy')
#
# @see #put_if_absent
def get_or_create(*args)
props = args.first
raise "Can't get or create entity since #{props.inspect} does not included unique key #{props[unique_factory_key]}'" unless props[unique_factory_key]
index = index_for_type(_decl_props[unique_factory_key][:index])
Neo4j::Core::Index::UniqueFactory.new(unique_factory_key, index) { |*| create!(*args) }.get_or_create(unique_factory_key, props[unique_factory_key])
end
# Same as #create, but raises an error if there is a problem during save.
# @return [Neo4j::Rails::Model, Neo4j::Rails::Relationship]
def create!(*args)
new(*args).tap do |o|
yield o if block_given?
o.save!
end
end
# Destroy each node in turn. Runs the destroy callbacks for each node.
def destroy_all
all.each do |n|
n.destroy
end
end
end
# Returns if the entity is currently being updated or created
def create_or_updating?
!!@_create_or_updating
end
protected
def update
write_changed_attributes
clear_changes
true
end
def create_or_update
# since the same model can be created or updated twice from a relationship we have to have this guard
@_create_or_updating = true
result = persisted? ? update : create
unless result != false
Neo4j::Rails::Transaction.fail if Neo4j::Rails::Transaction.running?
false
else
true
end
ensure
@_create_or_updating = nil
end
def set_deleted_properties
@_deleted = true
end
public
class RecordInvalidError < RuntimeError
attr_reader :record
def initialize(record)
@record = record
super(@record.errors.full_messages.join(", "))
end
end
end
end
end