Skip to content

Commit

Permalink
Merge 4b986ac into c2707c9
Browse files Browse the repository at this point in the history
  • Loading branch information
subvertallchris committed May 4, 2015
2 parents c2707c9 + 4b986ac commit 899f46f
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 106 deletions.
2 changes: 2 additions & 0 deletions lib/neo4j.rb
Expand Up @@ -26,6 +26,8 @@
require 'neo4j/paginated'

require 'neo4j/shared/callbacks'
require 'neo4j/shared/declared_property'
require 'neo4j/shared/declared_property_manager'
require 'neo4j/shared/property'
require 'neo4j/shared/persistence'
require 'neo4j/shared/validations'
Expand Down
3 changes: 1 addition & 2 deletions lib/neo4j/active_node/initialize.rb
@@ -1,6 +1,5 @@
module Neo4j::ActiveNode::Initialize
extend ActiveSupport::Concern
include Neo4j::Shared::TypeConverters
attr_reader :called_by

# called when loading the node from the database
Expand All @@ -13,7 +12,7 @@ def init_on_load(persisted_node, properties)
attr = @attributes || self.class.attributes_nil_hash.dup
@attributes = attr.merge!(properties).stringify_keys!
self.default_properties = properties
@attributes = convert_properties_to :ruby, @attributes
@attributes = self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes)
end

# Implements the Neo4j::Node#wrapper and Neo4j::Relationship#wrapper method
Expand Down
5 changes: 2 additions & 3 deletions lib/neo4j/active_node/persistence.rb
Expand Up @@ -10,6 +10,7 @@ def initialize(record)
end

extend ActiveSupport::Concern
extend Forwardable
include Neo4j::Shared::Persistence

# Saves the model.
Expand Down Expand Up @@ -48,13 +49,11 @@ def create_model(*)
create_magic_properties
set_timestamps
create_magic_properties
properties = convert_properties_to :db, props
properties = self.class.declared_property_manager.convert_properties_to(self, :db, props)
node = _create_node(properties)
init_on_load(node, node.props)
send_props(@relationship_props) if @relationship_props
@relationship_props = nil
# Neo4j::IdentityMap.add(node, self)
# write_changed_relationships
true
end

Expand Down
3 changes: 1 addition & 2 deletions lib/neo4j/active_rel/initialize.rb
@@ -1,7 +1,6 @@
module Neo4j::ActiveRel
module Initialize
extend ActiveSupport::Concern
include Neo4j::Shared::TypeConverters

attr_reader :_persisted_obj

Expand All @@ -17,7 +16,7 @@ def init_on_load(persisted_rel, from_node_id, to_node_id, type)
@attributes = attributes.merge(persisted_rel.props.stringify_keys)
load_nodes(from_node_id, to_node_id)
self.default_properties = persisted_rel.props
@attributes = convert_properties_to :ruby, @attributes
@attributes = self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes)
end

# Implements the Neo4j::Node#wrapper and Neo4j::Relationship#wrapper method
Expand Down
2 changes: 1 addition & 1 deletion lib/neo4j/active_rel/persistence.rb
Expand Up @@ -22,7 +22,7 @@ def create_model(*)
validate_node_classes!
create_magic_properties
set_timestamps
properties = convert_properties_to :db, props
properties = self.class.declared_property_manager.convert_properties_to(self, :db, props)
rel = _create_rel(from_node, to_node, properties)
return self unless rel.respond_to?(:_persisted_obj)
init_on_load(rel._persisted_obj, from_node, to_node, @rel_type)
Expand Down
5 changes: 5 additions & 0 deletions lib/neo4j/shared.rb
Expand Up @@ -28,10 +28,15 @@ def neo4j_session

included do
self.include_root_in_json = Neo4j::Config.include_root_in_json
@_declared_property_manager ||= Neo4j::Shared::DeclaredPropertyManager.new(self)

def self.i18n_scope
:neo4j
end
end

def declared_property_manager
self.class.declared_property_manager
end
end
end
62 changes: 62 additions & 0 deletions lib/neo4j/shared/declared_property.rb
@@ -0,0 +1,62 @@
module Neo4j::Shared
# Contains methods related to the management
class DeclaredProperty
class IllegalPropertyError < StandardError; end

ILLEGAL_PROPS = %w(from_node to_node start_node end_node)
attr_reader :name, :name_string, :name_sym, :options, :magic_typecaster

def initialize(name, options = {})
fail IllegalPropertyError, "#{name} is an illegal property" if ILLEGAL_PROPS.include?(name.to_s)
@name = @name_sym = name
@name_string = name.to_s
@options = options
end

def register
register_magic_properties
end

def type
options[:type]
end

def typecaster
options[:typecaster]
end

def default_value
options[:default]
end

private

# Tweaks properties
def register_magic_properties
options[:type] ||= DateTime if name.to_sym == :created_at || name.to_sym == :updated_at
# TODO: Custom typecaster to fix the stuff below
# ActiveAttr does not handle "Time", Rails and Neo4j.rb 2.3 did
# Convert it to DateTime in the interest of consistency
options[:type] = DateTime if options[:type] == Time

register_magic_typecaster
register_type_converter
end

def register_magic_typecaster
found_typecaster = Neo4j::Shared::TypeConverters.typecaster_for(options[:type])
return unless found_typecaster && found_typecaster.respond_to?(:primitive_type)
options[:typecaster] = found_typecaster
@magic_typecaster = options[:type]
options[:type] = found_typecaster.primitive_type
end

def register_type_converter
converter = options[:serializer]
return unless converter
options[:type] = converter.convert_type
options[:typecaster] = ActiveAttr::Typecasting::ObjectTypecaster.new
Neo4j::Shared::TypeConverters.register_converter(converter)
end
end
end
94 changes: 94 additions & 0 deletions lib/neo4j/shared/declared_property_manager.rb
@@ -0,0 +1,94 @@
module Neo4j::Shared
class DeclaredPropertyManager
include Neo4j::Shared::TypeConverters

attr_reader :klass

def initialize(klass)
@klass = klass
end

def register(property)
@_attributes_nil_hash = nil
registered_properties[property.name] = property
register_magic_typecaster(property) if property.magic_typecaster
declared_property_defaults[property.name] = property.default_value if property.default_value
end

def declared_property_defaults
@_default_property_values ||= {}
end

def registered_properties
@_registered_properties ||= {}
end

def attributes_nil_hash
@_attributes_nil_hash ||= {}.tap { |attr_hash| registered_properties.each_pair { |k, _v| attr_hash[k.to_s] = nil } }.freeze
end

def unregister(name)
# might need to be include?(name.to_s)
fail ArgumentError, "Argument `#{name}` not an attribute" if not registered_properties[name]
declared_prop = registered_properties[name]
registered_properties.delete(declared_prop)
unregister_magic_typecaster(name)
unregister_property_default(name)
end

def serialize(name, coder = JSON)
@serialize ||= {}
@serialize[name] = coder
end

def serialized_properties=(serialize_hash)
@serialized_property_keys = nil
@serialize = serialize_hash.clone
end

def serialized_properties
@serialize ||= {}
end

def serialized_properties_keys
@serialized_property_keys ||= serialized_properties.keys
end

def magic_typecast_properties_keys
@magic_typecast_properties_keys ||= magic_typecast_properties.keys
end

def magic_typecast_properties
@magic_typecast_properties ||= {}
end

# The known mappings of declared properties and their primitive types.
def upstream_primitives
@upstream_primitives ||= {}
end

protected

# Prevents repeated calls to :_attribute_type, which isn't free and never changes.
def fetch_upstream_primitive(attr)
upstream_primitives[attr] || upstream_primitives[attr] = klass._attribute_type(attr)
end

private

def unregister_magic_typecaster(property)
magic_typecast_properties.delete(property)
@magic_typecast_properties_keys = nil
end

def unregister_property_default(property)
declared_property_defaults.delete(property)
@_default_property_values = nil
end

def register_magic_typecaster(property)
magic_typecast_properties[property.name] = property.magic_typecaster
@magic_typecast_properties_keys = nil
end
end
end
11 changes: 9 additions & 2 deletions lib/neo4j/shared/persistence.rb
@@ -1,15 +1,14 @@
module Neo4j::Shared
module Persistence
extend ActiveSupport::Concern
include Neo4j::Shared::TypeConverters

USES_CLASSNAME = []

def update_model
return if !changed_attributes || changed_attributes.empty?

changed_props = attributes.select { |k, _| changed_attributes.include?(k) }
changed_props = convert_properties_to :db, changed_props
changed_props = self.class.declared_property_manager.convert_properties_to(self, :db, changed_props)
_persisted_obj.update_props(changed_props)
changed_attributes.clear
end
Expand All @@ -33,6 +32,7 @@ def update_attribute!(attribute, value)
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
apply_default_values
result = _persisted_obj ? update_model : create_model
if result == false
Neo4j::Transaction.current.failure if Neo4j::Transaction.current
Expand All @@ -47,6 +47,13 @@ def create_or_update
@_create_or_updating = nil
end

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

# 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?
Expand Down
68 changes: 17 additions & 51 deletions lib/neo4j/shared/property.rb
Expand Up @@ -11,9 +11,7 @@ module Property

class UndefinedPropertyError < RuntimeError; end
class MultiparameterAssignmentError < StandardError; end
class IllegalPropertyError < StandardError; end

ILLEGAL_PROPS = %w(from_node to_node start_node end_node)
# @_declared_property_manager = DeclaredPropertyManager.new

attr_reader :_persisted_obj

Expand Down Expand Up @@ -123,6 +121,10 @@ def instantiate_object(field, values_with_empty_parameters)
end

module ClassMethods
extend Forwardable

def_delegators :declared_property_manager, :serialized_properties, :serialized_properties=, :serialize, :declared_property_defaults

# Defines a property on the class
#
# See active_attr gem for allowed options, e.g which type
Expand Down Expand Up @@ -152,21 +154,25 @@ module ClassMethods
# property :name, constraint: :unique
# end
def property(name, options = {})
@_attributes_nil_hash = nil
check_illegal_prop(name)
magic_properties(name, options)
attribute(name, options)
prop = DeclaredProperty.new(name, options)
prop.register
declared_property_manager.register(prop)

attribute(name, prop.options)
constraint_or_index(name, options)
end

def undef_property(name)
fail ArgumentError, "Argument `#{name}` not an attribute" if not attribute_names.include?(name.to_s)

declared_property_manager.unregister(name)
attribute_methods(name).each { |method| undef_method(method) }

undef_constraint_or_index(name)
end

def declared_property_manager
@_declared_property_manager ||= DeclaredPropertyManager.new(self)
end

# TODO: Move this to the DeclaredPropertyManager
def default_property(name, &block)
reset_default_properties(name) if default_properties.respond_to?(:size)
default_properties[name] = block
Expand Down Expand Up @@ -208,15 +214,7 @@ def attribute!(name, options = {})
# @return [Hash] A frozen hash of all model properties with nil values. It is used during node loading and prevents
# an extra call to a slow dependency method.
def attributes_nil_hash
@_attributes_nil_hash ||= {}.tap { |attr_hash| attribute_names.each { |k, _v| attr_hash[k.to_s] = nil } }.freeze
end

def magic_typecast_properties
@magic_typecast_properties ||= {}
end

def magic_typecast_properties_keys
@magic_typecast_properties_keys ||= magic_typecast_properties.keys
declared_property_manager.attributes_nil_hash
end

private
Expand All @@ -231,38 +229,6 @@ def constraint_or_index(name, options)
index(name, options) if options[:index] == :exact
end
end

def check_illegal_prop(name)
fail IllegalPropertyError, "#{name} is an illegal property" if ILLEGAL_PROPS.include?(name.to_s)
end

# Tweaks properties
def magic_properties(name, options)
magic_typecast(name, options)
type_converter(options)
options[:type] ||= DateTime if name.to_sym == :created_at || name.to_sym == :updated_at

# ActiveAttr does not handle "Time", Rails and Neo4j.rb 2.3 did
# Convert it to DateTime in the interest of consistency
options[:type] = DateTime if options[:type] == Time
end

def type_converter(options)
converter = options[:serializer]
return unless converter
options[:type] = converter.convert_type
options[:typecaster] = ActiveAttr::Typecasting::ObjectTypecaster.new
Neo4j::Shared::TypeConverters.register_converter(converter)
end

def magic_typecast(name, options)
typecaster = Neo4j::Shared::TypeConverters.typecaster_for(options[:type])
return unless typecaster && typecaster.respond_to?(:primitive_type)
magic_typecast_properties[name] = options[:type]
@magic_typecast_properties_keys = nil
options[:type] = typecaster.primitive_type
options[:typecaster] = typecaster
end
end
end
end

0 comments on commit 899f46f

Please sign in to comment.