activegraph 10.0.0.pre.alpha.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|