activegraph 10.0.0.pre.alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1989 -0
- data/CONTRIBUTORS +12 -0
- data/Gemfile +24 -0
- data/README.md +107 -0
- data/bin/rake +17 -0
- data/config/locales/en.yml +5 -0
- data/config/neo4j/add_classnames.yml +1 -0
- data/config/neo4j/config.yml +38 -0
- data/lib/neo4j.rb +116 -0
- data/lib/neo4j/active_base.rb +89 -0
- data/lib/neo4j/active_node.rb +108 -0
- data/lib/neo4j/active_node/callbacks.rb +8 -0
- data/lib/neo4j/active_node/dependent.rb +11 -0
- data/lib/neo4j/active_node/dependent/association_methods.rb +49 -0
- data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +51 -0
- data/lib/neo4j/active_node/enum.rb +26 -0
- data/lib/neo4j/active_node/has_n.rb +612 -0
- data/lib/neo4j/active_node/has_n/association.rb +278 -0
- data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
- data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
- data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
- data/lib/neo4j/active_node/id_property.rb +224 -0
- data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
- data/lib/neo4j/active_node/initialize.rb +21 -0
- data/lib/neo4j/active_node/labels.rb +207 -0
- data/lib/neo4j/active_node/labels/index.rb +37 -0
- data/lib/neo4j/active_node/labels/reloading.rb +21 -0
- data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
- data/lib/neo4j/active_node/node_wrapper.rb +54 -0
- data/lib/neo4j/active_node/orm_adapter.rb +82 -0
- data/lib/neo4j/active_node/persistence.rb +187 -0
- data/lib/neo4j/active_node/property.rb +60 -0
- data/lib/neo4j/active_node/query.rb +76 -0
- data/lib/neo4j/active_node/query/query_proxy.rb +374 -0
- data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +177 -0
- data/lib/neo4j/active_node/query/query_proxy_eager_loading/association_tree.rb +75 -0
- data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +110 -0
- data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
- data/lib/neo4j/active_node/query/query_proxy_link.rb +139 -0
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +302 -0
- data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +86 -0
- data/lib/neo4j/active_node/query_methods.rb +68 -0
- data/lib/neo4j/active_node/reflection.rb +86 -0
- data/lib/neo4j/active_node/rels.rb +11 -0
- data/lib/neo4j/active_node/scope.rb +166 -0
- data/lib/neo4j/active_node/unpersisted.rb +48 -0
- data/lib/neo4j/active_node/validations.rb +59 -0
- data/lib/neo4j/active_rel.rb +67 -0
- data/lib/neo4j/active_rel/callbacks.rb +15 -0
- data/lib/neo4j/active_rel/initialize.rb +28 -0
- data/lib/neo4j/active_rel/persistence.rb +134 -0
- data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
- data/lib/neo4j/active_rel/property.rb +95 -0
- data/lib/neo4j/active_rel/query.rb +101 -0
- data/lib/neo4j/active_rel/rel_wrapper.rb +31 -0
- data/lib/neo4j/active_rel/related_node.rb +87 -0
- data/lib/neo4j/active_rel/types.rb +82 -0
- data/lib/neo4j/active_rel/validations.rb +8 -0
- data/lib/neo4j/ansi.rb +14 -0
- data/lib/neo4j/class_arguments.rb +39 -0
- data/lib/neo4j/config.rb +135 -0
- data/lib/neo4j/core.rb +14 -0
- data/lib/neo4j/core/connection_failed_error.rb +6 -0
- data/lib/neo4j/core/cypher_error.rb +37 -0
- data/lib/neo4j/core/driver.rb +66 -0
- data/lib/neo4j/core/has_uri.rb +63 -0
- data/lib/neo4j/core/instrumentable.rb +36 -0
- data/lib/neo4j/core/label.rb +158 -0
- data/lib/neo4j/core/logging.rb +44 -0
- data/lib/neo4j/core/node.rb +23 -0
- data/lib/neo4j/core/querable.rb +88 -0
- data/lib/neo4j/core/query.rb +487 -0
- data/lib/neo4j/core/query_builder.rb +32 -0
- data/lib/neo4j/core/query_clauses.rb +727 -0
- data/lib/neo4j/core/query_ext.rb +24 -0
- data/lib/neo4j/core/query_find_in_batches.rb +49 -0
- data/lib/neo4j/core/relationship.rb +13 -0
- data/lib/neo4j/core/responses.rb +50 -0
- data/lib/neo4j/core/result.rb +33 -0
- data/lib/neo4j/core/schema.rb +30 -0
- data/lib/neo4j/core/schema_errors.rb +12 -0
- data/lib/neo4j/core/wrappable.rb +30 -0
- data/lib/neo4j/errors.rb +57 -0
- data/lib/neo4j/migration.rb +148 -0
- data/lib/neo4j/migrations.rb +27 -0
- data/lib/neo4j/migrations/base.rb +77 -0
- data/lib/neo4j/migrations/check_pending.rb +20 -0
- data/lib/neo4j/migrations/helpers.rb +105 -0
- data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
- data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
- data/lib/neo4j/migrations/helpers/schema.rb +51 -0
- data/lib/neo4j/migrations/migration_file.rb +24 -0
- data/lib/neo4j/migrations/runner.rb +195 -0
- data/lib/neo4j/migrations/schema.rb +44 -0
- data/lib/neo4j/migrations/schema_migration.rb +14 -0
- data/lib/neo4j/model_schema.rb +139 -0
- data/lib/neo4j/paginated.rb +27 -0
- data/lib/neo4j/railtie.rb +105 -0
- data/lib/neo4j/schema/operation.rb +102 -0
- data/lib/neo4j/shared.rb +60 -0
- data/lib/neo4j/shared/attributes.rb +216 -0
- data/lib/neo4j/shared/callbacks.rb +68 -0
- data/lib/neo4j/shared/cypher.rb +37 -0
- data/lib/neo4j/shared/declared_properties.rb +204 -0
- data/lib/neo4j/shared/declared_property.rb +109 -0
- data/lib/neo4j/shared/declared_property/index.rb +37 -0
- data/lib/neo4j/shared/enum.rb +167 -0
- data/lib/neo4j/shared/filtered_hash.rb +79 -0
- data/lib/neo4j/shared/identity.rb +34 -0
- data/lib/neo4j/shared/initialize.rb +64 -0
- data/lib/neo4j/shared/marshal.rb +23 -0
- data/lib/neo4j/shared/mass_assignment.rb +64 -0
- data/lib/neo4j/shared/permitted_attributes.rb +28 -0
- data/lib/neo4j/shared/persistence.rb +282 -0
- data/lib/neo4j/shared/property.rb +240 -0
- data/lib/neo4j/shared/query_factory.rb +102 -0
- data/lib/neo4j/shared/rel_type_converters.rb +43 -0
- data/lib/neo4j/shared/serialized_properties.rb +30 -0
- data/lib/neo4j/shared/type_converters.rb +433 -0
- data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
- data/lib/neo4j/shared/typecaster.rb +53 -0
- data/lib/neo4j/shared/validations.rb +44 -0
- data/lib/neo4j/tasks/migration.rake +202 -0
- data/lib/neo4j/timestamps.rb +11 -0
- data/lib/neo4j/timestamps/created.rb +9 -0
- data/lib/neo4j/timestamps/updated.rb +9 -0
- data/lib/neo4j/transaction.rb +139 -0
- data/lib/neo4j/type_converters.rb +7 -0
- data/lib/neo4j/undeclared_properties.rb +53 -0
- data/lib/neo4j/version.rb +3 -0
- data/lib/neo4j/wrapper.rb +4 -0
- data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
- data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
- data/lib/rails/generators/neo4j/model/model_generator.rb +88 -0
- data/lib/rails/generators/neo4j/model/templates/migration.erb +9 -0
- data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
- data/lib/rails/generators/neo4j/upgrade_v8/templates/migration.erb +17 -0
- data/lib/rails/generators/neo4j/upgrade_v8/upgrade_v8_generator.rb +32 -0
- data/lib/rails/generators/neo4j_generator.rb +119 -0
- data/neo4j.gemspec +51 -0
- metadata +421 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Neo4j::ActiveNode::Labels
|
|
2
|
+
module Index
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
module ClassMethods
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
def_delegators :declared_properties, :indexed_properties
|
|
9
|
+
|
|
10
|
+
# Creates a Neo4j index on given property
|
|
11
|
+
#
|
|
12
|
+
# This can also be done on the property directly, see Neo4j::ActiveNode::Property::ClassMethods#property.
|
|
13
|
+
#
|
|
14
|
+
# @param [Symbol] property the property we want a Neo4j index on
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# class Person
|
|
18
|
+
# include Neo4j::ActiveNode
|
|
19
|
+
# property :name
|
|
20
|
+
# index :name
|
|
21
|
+
# end
|
|
22
|
+
def index(property)
|
|
23
|
+
return if Neo4j::ModelSchema.defined_constraint?(self, property)
|
|
24
|
+
|
|
25
|
+
Neo4j::ModelSchema.add_defined_index(self, property)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Creates a neo4j constraint on this class for given property
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# Person.constraint :name, type: :unique
|
|
32
|
+
def constraint(property, _constraints = {type: :unique})
|
|
33
|
+
Neo4j::ModelSchema.add_defined_constraint(self, property)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Neo4j::ActiveNode::Labels
|
|
2
|
+
module Reloading
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
MODELS_TO_RELOAD = []
|
|
6
|
+
|
|
7
|
+
def self.reload_models!
|
|
8
|
+
MODELS_TO_RELOAD.each(&:constantize)
|
|
9
|
+
MODELS_TO_RELOAD.clear
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def before_remove_const
|
|
14
|
+
associations.each_value(&:queue_model_refresh!)
|
|
15
|
+
MODELS_FOR_LABELS_CACHE.clear
|
|
16
|
+
WRAPPED_CLASSES.each { |c| MODELS_TO_RELOAD << c.name }
|
|
17
|
+
WRAPPED_CLASSES.clear
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Neo4j::ActiveNode
|
|
2
|
+
class NodeListFormatter
|
|
3
|
+
def initialize(list, max_elements = 5)
|
|
4
|
+
@list = list
|
|
5
|
+
@max_elements = max_elements
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def inspect
|
|
9
|
+
return @list.inspect if !@max_elements || @list.length <= @max_elements
|
|
10
|
+
"[#{@list.take(5).map!(&:inspect).join(', ')}, ...]"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'active_support/inflector'
|
|
2
|
+
require 'neo4j/core/node'
|
|
3
|
+
|
|
4
|
+
wrapping_proc = proc do |node|
|
|
5
|
+
found_class = Neo4j::NodeWrapping.class_to_wrap(node.labels)
|
|
6
|
+
next node if not found_class
|
|
7
|
+
|
|
8
|
+
found_class.new.tap do |wrapped_node|
|
|
9
|
+
wrapped_node.init_on_load(node, node.props)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
Neo4j::Driver::Types::Node.wrapper_callback(wrapping_proc)
|
|
13
|
+
|
|
14
|
+
module Neo4j
|
|
15
|
+
module NodeWrapping
|
|
16
|
+
# Only load classes once for performance
|
|
17
|
+
CONSTANTS_FOR_LABELS_CACHE = {}
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def class_to_wrap(labels)
|
|
21
|
+
load_classes_from_labels(labels)
|
|
22
|
+
Neo4j::ActiveNode::Labels.model_for_labels(labels).tap do |model_class|
|
|
23
|
+
populate_constants_for_labels_cache(model_class, labels)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def load_classes_from_labels(labels)
|
|
30
|
+
labels.each { |label| constant_for_label(label) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def constant_for_label(label)
|
|
34
|
+
CONSTANTS_FOR_LABELS_CACHE[label] || CONSTANTS_FOR_LABELS_CACHE[label] = constantized_label(label)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def constantized_label(label)
|
|
38
|
+
"#{association_model_namespace}::#{label}".constantize
|
|
39
|
+
rescue NameError, LoadError
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def populate_constants_for_labels_cache(model_class, labels)
|
|
44
|
+
labels.each do |label|
|
|
45
|
+
CONSTANTS_FOR_LABELS_CACHE[label] = model_class if CONSTANTS_FOR_LABELS_CACHE[label].nil?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def association_model_namespace
|
|
50
|
+
Neo4j::Config.association_model_namespace_string
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'orm_adapter'
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module ActiveNode
|
|
5
|
+
module ClassMethods
|
|
6
|
+
include OrmAdapter::ToAdapter
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class OrmAdapter < ::OrmAdapter::Base
|
|
10
|
+
module ClassMethods
|
|
11
|
+
include ActiveModel::Callbacks
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def column_names
|
|
15
|
+
klass._decl_props.keys
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def i18n_scope
|
|
19
|
+
:neo4j
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get an instance by id of the model
|
|
23
|
+
def get!(id)
|
|
24
|
+
klass.find(wrap_key(id)).tap do |node|
|
|
25
|
+
fail 'No record found' if node.nil?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get an instance by id of the model
|
|
30
|
+
def get(id)
|
|
31
|
+
klass.find_by(klass.id_property_name => wrap_key(id))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Find the first instance matching conditions
|
|
35
|
+
def find_first(options = {})
|
|
36
|
+
conditions, order = extract_conditions!(options)
|
|
37
|
+
extract_id!(conditions)
|
|
38
|
+
order = hasherize_order(order)
|
|
39
|
+
|
|
40
|
+
result = klass.where(conditions)
|
|
41
|
+
result = result.order(order) unless order.empty?
|
|
42
|
+
result.first
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Find all models matching conditions
|
|
46
|
+
def find_all(options = {})
|
|
47
|
+
conditions, order, limit, offset = extract_conditions!(options)
|
|
48
|
+
extract_id!(conditions)
|
|
49
|
+
order = hasherize_order(order)
|
|
50
|
+
|
|
51
|
+
result = klass.where(conditions)
|
|
52
|
+
result = result.order(order) unless order.empty?
|
|
53
|
+
result = result.skip(offset) if offset
|
|
54
|
+
result = result.limit(limit) if limit
|
|
55
|
+
result.to_a
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Create a model using attributes
|
|
59
|
+
def create!(attributes = {})
|
|
60
|
+
klass.create!(attributes)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @see OrmAdapter::Base#destroy
|
|
64
|
+
def destroy(object)
|
|
65
|
+
object.destroy && true if valid_object?(object)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def hasherize_order(order)
|
|
71
|
+
(order || []).map { |clause| Hash[*clause] }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def extract_id!(conditions)
|
|
75
|
+
id = conditions.delete(:id)
|
|
76
|
+
return if not id
|
|
77
|
+
|
|
78
|
+
conditions[klass.id_property_name.to_sym] = id
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
module Neo4j::ActiveNode
|
|
2
|
+
module Persistence
|
|
3
|
+
class RecordInvalidError < RuntimeError
|
|
4
|
+
attr_reader :record
|
|
5
|
+
|
|
6
|
+
def initialize(record)
|
|
7
|
+
@record = record
|
|
8
|
+
super(@record.errors.full_messages.join(', '))
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
extend ActiveSupport::Concern
|
|
13
|
+
extend Forwardable
|
|
14
|
+
include Neo4j::Shared::Persistence
|
|
15
|
+
|
|
16
|
+
# Saves the model.
|
|
17
|
+
#
|
|
18
|
+
# If the model is new a record gets created in the database, otherwise the existing record gets updated.
|
|
19
|
+
# If perform_validation is true validations run.
|
|
20
|
+
# If any of them fail the action is cancelled and save returns false.
|
|
21
|
+
# If the flag is false validations are bypassed altogether.
|
|
22
|
+
# See ActiveRecord::Validations for more information.
|
|
23
|
+
# There's a series of callbacks associated with save.
|
|
24
|
+
# If any of the before_* callbacks return false the action is cancelled and save returns false.
|
|
25
|
+
def save(*)
|
|
26
|
+
cascade_save do
|
|
27
|
+
association_proxy_cache.clear
|
|
28
|
+
create_or_update
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Increments concurrently a numeric attribute by a centain amount
|
|
33
|
+
# @param [Symbol, String] name of the attribute to increment
|
|
34
|
+
# @param [Integer, Float] amount to increment
|
|
35
|
+
def concurrent_increment!(attribute, by = 1)
|
|
36
|
+
increment_by_query! query_as(:n), attribute, by
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Persist the object to the database. Validations and Callbacks are included
|
|
40
|
+
# by default but validation can be disabled by passing :validate => false
|
|
41
|
+
# to #save! Creates a new transaction.
|
|
42
|
+
#
|
|
43
|
+
# @raise a RecordInvalidError if there is a problem during save.
|
|
44
|
+
# @param (see Neo4j::Rails::Validations#save)
|
|
45
|
+
# @return nil
|
|
46
|
+
# @see #save
|
|
47
|
+
# @see Neo4j::Rails::Validations Neo4j::Rails::Validations - for the :validate parameter
|
|
48
|
+
# @see Neo4j::Rails::Callbacks Neo4j::Rails::Callbacks - for callbacks
|
|
49
|
+
def save!(*args)
|
|
50
|
+
save(*args) or fail(RecordInvalidError, self) # rubocop:disable Style/AndOr
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Creates a model with values matching those of the instance attributes and returns its id.
|
|
54
|
+
# @private
|
|
55
|
+
# @return true
|
|
56
|
+
def create_model
|
|
57
|
+
node = _create_node(props_for_create)
|
|
58
|
+
init_on_load(node, node.props)
|
|
59
|
+
@deferred_nodes = nil
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# TODO: This does not seem like it should be the responsibility of the node.
|
|
64
|
+
# Creates an unwrapped node in the database.
|
|
65
|
+
# @param [Hash] node_props The type-converted properties to be added to the new node.
|
|
66
|
+
# @param [Array] labels The labels to use for creating the new node.
|
|
67
|
+
# @return [Neo4j::Node] A CypherNode or EmbeddedNode
|
|
68
|
+
def _create_node(node_props, labels = labels_for_create)
|
|
69
|
+
query = "CREATE (n:`#{Array(labels).join('`:`')}`) SET n = {props} RETURN n"
|
|
70
|
+
neo4j_query(query, {props: node_props}, wrap_level: :core_entity).to_a[0].n
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# As the name suggests, this inserts the primary key (id property) into the properties hash.
|
|
74
|
+
# The method called here, `default_property_values`, is a holdover from an earlier version of the gem. It does NOT
|
|
75
|
+
# contain the default values of properties, it contains the Default Property, which we now refer to as the ID Property.
|
|
76
|
+
# It will be deprecated and renamed in a coming refactor.
|
|
77
|
+
# @param [Hash] converted_props A hash of properties post-typeconversion, ready for insertion into the DB.
|
|
78
|
+
def inject_primary_key!(converted_props)
|
|
79
|
+
self.class.default_property_values(self).tap do |destination_props|
|
|
80
|
+
destination_props.merge!(converted_props) if converted_props.is_a?(Hash)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Array] Labels to be set on the node during a create event
|
|
85
|
+
def labels_for_create
|
|
86
|
+
self.class.mapped_label_names
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def destroy_query
|
|
92
|
+
query_as(:n).break.optional_match('(n)-[r]-()').delete(:n, :r)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The pending associations are cleared during the save process, so it's necessary to
|
|
96
|
+
# build the processable hash before it begins. If there are nodes and associations that
|
|
97
|
+
# need to be created after the node is saved, a new transaction is started.
|
|
98
|
+
def cascade_save
|
|
99
|
+
self.class.run_transaction(pending_deferred_creations?) do
|
|
100
|
+
yield.tap { process_unpersisted_nodes! }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module ClassMethods
|
|
105
|
+
# Creates and saves a new node
|
|
106
|
+
# @param [Hash] props the properties the new node should have
|
|
107
|
+
def create(props = {})
|
|
108
|
+
new(props).tap do |obj|
|
|
109
|
+
yield obj if block_given?
|
|
110
|
+
obj.save
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Same as #create, but raises an error if there is a problem during save.
|
|
115
|
+
def create!(props = {})
|
|
116
|
+
new(props).tap do |o|
|
|
117
|
+
yield o if block_given?
|
|
118
|
+
o.save!
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def merge(match_attributes, optional_attrs = {})
|
|
123
|
+
options = [:on_create, :on_match, :set]
|
|
124
|
+
optional_attrs.assert_valid_keys(*options)
|
|
125
|
+
|
|
126
|
+
optional_attrs.default = {}
|
|
127
|
+
on_create_attrs, on_match_attrs, set_attrs = optional_attrs.values_at(*options)
|
|
128
|
+
|
|
129
|
+
new_query.merge(n: {self.mapped_label_names => match_attributes})
|
|
130
|
+
.on_create_set(on_create_clause(on_create_attrs))
|
|
131
|
+
.on_match_set(on_match_clause(on_match_attrs))
|
|
132
|
+
.break.set(n: set_attrs)
|
|
133
|
+
.pluck(:n).first
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def find_or_create(find_attributes, set_attributes = {})
|
|
137
|
+
on_create_attributes = set_attributes.reverse_merge(find_attributes.merge(self.new(find_attributes).props_for_create))
|
|
138
|
+
|
|
139
|
+
new_query.merge(n: {self.mapped_label_names => find_attributes})
|
|
140
|
+
.on_create_set(n: on_create_attributes)
|
|
141
|
+
.pluck(:n).first
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Finds the first node with the given attributes, or calls create if none found
|
|
145
|
+
def find_or_create_by(attributes, &block)
|
|
146
|
+
find_by(attributes) || create(attributes, &block)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Same as #find_or_create_by, but calls #create! so it raises an error if there is a problem during save.
|
|
150
|
+
def find_or_create_by!(attributes, &block)
|
|
151
|
+
find_by(attributes) || create!(attributes, &block)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def find_or_initialize_by(attributes)
|
|
155
|
+
find_by(attributes) || new(attributes).tap { |o| yield(o) if block_given? }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def load_entity(id)
|
|
159
|
+
query = query_base_for(id, :n).return(:n)
|
|
160
|
+
result = neo4j_query(query).first
|
|
161
|
+
result && result.n
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def query_base_for(neo_id, var = :n)
|
|
165
|
+
Neo4j::ActiveBase.new_query.match(var).where(var => {neo_id: neo_id})
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def on_create_clause(clause)
|
|
171
|
+
if clause.is_a?(Hash)
|
|
172
|
+
{n: clause.merge(self.new(clause).props_for_create)}
|
|
173
|
+
else
|
|
174
|
+
clause
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def on_match_clause(clause)
|
|
179
|
+
if clause.is_a?(Hash)
|
|
180
|
+
{n: clause.merge(attributes_nil_hash.key?('updated_at') ? {updated_at: Time.new.to_i} : {})}
|
|
181
|
+
else
|
|
182
|
+
clause
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Neo4j::ActiveNode
|
|
2
|
+
module Property
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
include Neo4j::Shared::Property
|
|
5
|
+
|
|
6
|
+
def initialize(attributes = nil)
|
|
7
|
+
super(attributes)
|
|
8
|
+
@attributes ||= Hash[self.class.attributes_nil_hash]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
# Extracts keys from attributes hash which are associations of the model
|
|
13
|
+
# TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save?
|
|
14
|
+
def extract_association_attributes!(attributes)
|
|
15
|
+
return unless contains_association?(attributes)
|
|
16
|
+
attributes.each_with_object({}) do |(key, _), result|
|
|
17
|
+
result[key] = attributes.delete(key) if self.association_key?(key)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def association_key?(key)
|
|
22
|
+
association_method_keys.include?(key.to_sym)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def contains_association?(attributes)
|
|
28
|
+
return false unless attributes
|
|
29
|
+
attributes.each_key { |k| return true if association_key?(k) }
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# All keys which could be association setter methods (including _id/_ids)
|
|
34
|
+
def association_method_keys
|
|
35
|
+
@association_method_keys ||=
|
|
36
|
+
associations_keys.map(&:to_sym) +
|
|
37
|
+
associations.values.map do |association|
|
|
38
|
+
if association.type == :has_one
|
|
39
|
+
"#{association.name}_id"
|
|
40
|
+
elsif association.type == :has_many
|
|
41
|
+
"#{association.name.to_s.singularize}_ids"
|
|
42
|
+
end.to_sym
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def inspect_attributes
|
|
50
|
+
id_property_name = self.class.id_property_name.to_s
|
|
51
|
+
|
|
52
|
+
attribute_pairs = attributes.except(id_property_name).sort.map do |key, value|
|
|
53
|
+
[key, (value.is_a?(String) && value.size > 100) ? value.dup[0..100] : value]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
attribute_pairs.unshift([id_property_name, self.send(id_property_name)])
|
|
57
|
+
attribute_pairs
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|