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,28 @@
|
|
|
1
|
+
module Neo4j::ActiveRel
|
|
2
|
+
module Initialize
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
include Neo4j::Shared::Initialize
|
|
5
|
+
|
|
6
|
+
# called when loading the rel from the database
|
|
7
|
+
# @param [Neo4j::Embedded::EmbeddedRelationship, Neo4j::Server::CypherRelationship] persisted_rel properties of this relationship
|
|
8
|
+
# @param [Neo4j::Relationship] from_node_id The neo_id of the starting node of this rel
|
|
9
|
+
# @param [Neo4j::Relationship] to_node_id The neo_id of the ending node of this rel
|
|
10
|
+
# @param [String] type the relationship type
|
|
11
|
+
def init_on_load(persisted_rel, from_node_id, to_node_id, type)
|
|
12
|
+
@rel_type = type
|
|
13
|
+
@_persisted_obj = persisted_rel
|
|
14
|
+
changed_attributes_clear!
|
|
15
|
+
@attributes = convert_and_assign_attributes(persisted_rel.props)
|
|
16
|
+
load_nodes(from_node_id, to_node_id)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def init_on_reload(unwrapped_reloaded)
|
|
20
|
+
@attributes = nil
|
|
21
|
+
init_on_load(unwrapped_reloaded,
|
|
22
|
+
unwrapped_reloaded.start_node_id,
|
|
23
|
+
unwrapped_reloaded.end_node_id,
|
|
24
|
+
unwrapped_reloaded.rel_type)
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module Neo4j::ActiveRel
|
|
2
|
+
module Persistence
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
include Neo4j::Shared::Cypher::RelIdentifiers
|
|
5
|
+
include Neo4j::Shared::Persistence
|
|
6
|
+
|
|
7
|
+
class RelInvalidError < RuntimeError; end
|
|
8
|
+
class ModelClassInvalidError < RuntimeError; end
|
|
9
|
+
class RelCreateFailedError < RuntimeError; end
|
|
10
|
+
|
|
11
|
+
def from_node_identifier
|
|
12
|
+
@from_node_identifier || :from_node
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_node_identifier
|
|
16
|
+
@to_node_identifier || :to_node
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def from_node_identifier=(id)
|
|
20
|
+
@from_node_identifier = id.to_sym
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_node_identifier=(id)
|
|
24
|
+
@to_node_identifier = id.to_sym
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cypher_identifier
|
|
28
|
+
@cypher_identifier || :rel
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def save(*)
|
|
32
|
+
create_or_update
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def save!(*args)
|
|
36
|
+
save(*args) or fail(RelInvalidError, inspect) # rubocop:disable Style/AndOr
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Increments concurrently a numeric attribute by a centain amount
|
|
40
|
+
# @param [Symbol, String] name of the attribute to increment
|
|
41
|
+
# @param [Integer, Float] amount to increment
|
|
42
|
+
def concurrent_increment!(attribute, by = 1)
|
|
43
|
+
increment_by_query! query_as(:r), attribute, by, :r
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create_model
|
|
47
|
+
validate_node_classes!
|
|
48
|
+
validate_has_one_rel
|
|
49
|
+
rel = _create_rel
|
|
50
|
+
return self unless rel.respond_to?(:props)
|
|
51
|
+
init_on_load(rel, from_node, to_node, @rel_type)
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate_has_one_rel
|
|
56
|
+
return unless Neo4j::Config[:enforce_has_one]
|
|
57
|
+
to_node.validate_reverse_has_one_active_rel(self, :in, from_node) if to_node.persisted?
|
|
58
|
+
from_node.validate_reverse_has_one_active_rel(self, :out, to_node) if from_node.persisted?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def query_as(var)
|
|
62
|
+
# This should query based on the nodes, not the rel neo_id, I think
|
|
63
|
+
# Also, picky point: Should the var be `n`?
|
|
64
|
+
self.class.query_as(neo_id, var)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
module ClassMethods
|
|
68
|
+
# Creates a new relationship between objects
|
|
69
|
+
# @param [Hash] props the properties the new relationship should have
|
|
70
|
+
def create(*args)
|
|
71
|
+
new(*args).tap(&:save)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Same as #create, but raises an error if there is a problem during save.
|
|
75
|
+
def create!(*args)
|
|
76
|
+
new(*args).tap(&:save!)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def create_method
|
|
80
|
+
creates_unique? ? :create_unique : :create
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def load_entity(id)
|
|
84
|
+
query_as(id).pluck(:r).first
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def query_as(neo_id, var = :r)
|
|
88
|
+
Neo4j::ActiveBase.new_query.match("()-[#{var}]->()").where(var => {neo_id: neo_id})
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def create_method
|
|
93
|
+
self.class.create_method
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def destroy_query
|
|
99
|
+
query_as(:r).delete(:r)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def validate_node_classes!
|
|
103
|
+
[from_node, to_node].each do |node|
|
|
104
|
+
type = from_node == node ? :_from_class : :_to_class
|
|
105
|
+
type_class = self.class.send(type)
|
|
106
|
+
|
|
107
|
+
unless valid_type?(type_class, node)
|
|
108
|
+
fail ModelClassInvalidError, type_validation_error_message(node, type_class)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def valid_type?(type_object, node)
|
|
114
|
+
case type_object
|
|
115
|
+
when false, :any
|
|
116
|
+
true
|
|
117
|
+
when Array
|
|
118
|
+
type_object.any? { |c| valid_type?(c, node) }
|
|
119
|
+
else
|
|
120
|
+
node.class.mapped_label_names.include?(type_object.to_s.constantize.mapped_label_name)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def type_validation_error_message(node, type_class)
|
|
125
|
+
"Node class was #{node.class} (#{node.class.object_id}), expected #{type_class} (#{type_class.object_id})"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def _create_rel
|
|
129
|
+
factory = QueryFactory.new(from_node, to_node, self)
|
|
130
|
+
factory.build!
|
|
131
|
+
factory.unwrapped_rel
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Neo4j::ActiveRel::Persistence
|
|
2
|
+
# This class builds and executes a Cypher query, using information from the graph objects to determine
|
|
3
|
+
# whether they need to be created simultaneously.
|
|
4
|
+
# It keeps the rel instance from being responsible for inspecting the nodes or talking with Shared::QueryFactory.
|
|
5
|
+
class QueryFactory
|
|
6
|
+
NODE_SYMBOLS = [:from_node, :to_node]
|
|
7
|
+
attr_reader :from_node, :to_node, :rel, :unwrapped_rel
|
|
8
|
+
|
|
9
|
+
def initialize(from_node, to_node, rel)
|
|
10
|
+
@from_node = from_node
|
|
11
|
+
@to_node = to_node
|
|
12
|
+
@rel = rel
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# TODO: This feels like it should also wrap the rel, but that is handled in Neo4j::ActiveRel::Persistence at the moment.
|
|
16
|
+
# Builds and executes the query using the objects giving during init.
|
|
17
|
+
# It holds the process:
|
|
18
|
+
# * Execute node callbacks if needed
|
|
19
|
+
# * Create and execute the query
|
|
20
|
+
# * Mix the query response into the unpersisted objects given during init
|
|
21
|
+
def build!
|
|
22
|
+
node_before_callbacks! do
|
|
23
|
+
res = query_factory(rel, rel_id, iterative_query).query.unwrapped.return(*unpersisted_return_ids).first
|
|
24
|
+
node_symbols.each { |n| wrap!(send(n), res, n) }
|
|
25
|
+
@unwrapped_rel = res.send(rel_id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def rel_id
|
|
32
|
+
@rel_id ||= rel.rel_identifier
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Node callbacks only need to be executed if the node is not persisted. We let the `conditional_callback` method do the work,
|
|
36
|
+
# we only have to give it the type of callback we expect to be run and the condition which, if true, will prevent it from executing.
|
|
37
|
+
def node_before_callbacks!
|
|
38
|
+
validate_unpersisted_nodes!
|
|
39
|
+
from_node.conditional_callback(:create, from_node.persisted?) do
|
|
40
|
+
to_node.conditional_callback(:create, to_node.persisted?) do
|
|
41
|
+
yield
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_unpersisted_nodes!
|
|
47
|
+
node_symbols.each do |s|
|
|
48
|
+
obj = send(s)
|
|
49
|
+
next if obj.persisted?
|
|
50
|
+
fail RelCreateFailedError, "Cannot create rel with unpersisted, invalid #{s}" unless obj.valid?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Each node must be either created or matched before the relationship can be created. This class does not know or care about
|
|
55
|
+
# how that happens, it just knows that it needs a usable Neo4j::Core::Query object to do that.
|
|
56
|
+
# This method is "iterative" because it creates one factory for each node but the second builds upon the first.
|
|
57
|
+
def iterative_query
|
|
58
|
+
node_symbols.inject(false) do |iterative_query, sym|
|
|
59
|
+
obj = send(sym)
|
|
60
|
+
query_factory(obj, sym, iterative_query)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Isolates the dependency to the shared class. This has an awareness of Neo4j::Core::Query and will match or create
|
|
65
|
+
# based on the current state of the object passed in.
|
|
66
|
+
def query_factory(obj, sym, factory = false)
|
|
67
|
+
Neo4j::Shared::QueryFactory.create(obj, sym).tap do |factory_instance|
|
|
68
|
+
factory_instance.base_query = factory.blank? ? false : factory.query
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Array<Symbol>] The Cypher identifiers that will be returned from the query.
|
|
73
|
+
# We only need to return objects from our query that were created during it, otherwise we impact performance.
|
|
74
|
+
def unpersisted_return_ids
|
|
75
|
+
[rel_id].tap do |result|
|
|
76
|
+
node_symbols.each { |k| result << k unless send(k).persisted? }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param [Neo4j::ActiveNode] node A node, persisted or unpersisted
|
|
81
|
+
# @param [Struct] res The result of calling `return` on a Neo4j::Core::Query object. It responds to the same keys
|
|
82
|
+
# as our graph objects. If the object is unpersisted and was created during the query, the unwrapped node is mixed
|
|
83
|
+
# in, making the object reflect as "persisted".
|
|
84
|
+
# @param [Symbol] key :from_node or :to_node, the object to request from the response.
|
|
85
|
+
def wrap!(node, res, key)
|
|
86
|
+
return if node.persisted? || !res.respond_to?(key)
|
|
87
|
+
unwrapped = res.send(key)
|
|
88
|
+
node.init_on_load(unwrapped, unwrapped.props)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def node_symbols
|
|
92
|
+
self.class::NODE_SYMBOLS
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'neo4j/class_arguments'
|
|
2
|
+
|
|
3
|
+
module Neo4j::ActiveRel
|
|
4
|
+
module Property
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
include Neo4j::Shared::Property
|
|
7
|
+
|
|
8
|
+
%w(to_node from_node).each do |direction|
|
|
9
|
+
define_method(direction.to_s) { instance_variable_get("@#{direction}") }
|
|
10
|
+
define_method("#{direction}=") do |argument|
|
|
11
|
+
fail FrozenRelError, 'Relationship start/end nodes cannot be changed once persisted' if _persisted_obj
|
|
12
|
+
instance_variable_set("@#{direction}", argument)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
alias start_node from_node
|
|
17
|
+
alias end_node to_node
|
|
18
|
+
|
|
19
|
+
%w(start_node end_node).each do |direction|
|
|
20
|
+
define_method("#{direction}_neo_id") { send(direction).neo_id if direction }
|
|
21
|
+
end
|
|
22
|
+
alias from_node_neo_id start_node_neo_id
|
|
23
|
+
alias to_node_neo_id end_node_neo_id
|
|
24
|
+
|
|
25
|
+
# @return [String] a string representing the relationship type that will be created
|
|
26
|
+
def type
|
|
27
|
+
self.class.type
|
|
28
|
+
end
|
|
29
|
+
alias rel_type type
|
|
30
|
+
|
|
31
|
+
def initialize(attributes = nil)
|
|
32
|
+
super(attributes)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def creates_unique_option
|
|
36
|
+
self.class.creates_unique_option
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module ClassMethods
|
|
40
|
+
include Neo4j::Shared::Cypher::CreateMethod
|
|
41
|
+
|
|
42
|
+
# Extracts keys from attributes hash which are relationships of the model
|
|
43
|
+
# TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save?
|
|
44
|
+
def extract_association_attributes!(attributes)
|
|
45
|
+
return if attributes.blank?
|
|
46
|
+
{}.tap do |relationship_props|
|
|
47
|
+
attributes.each_key do |key|
|
|
48
|
+
relationship_props[key] = attributes.delete(key) if [:from_node, :to_node].include?(key)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def id_property_name
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
%w(to_class from_class).each do |direction|
|
|
58
|
+
define_method(direction.to_s) do |argument = nil|
|
|
59
|
+
if !argument.nil?
|
|
60
|
+
Neo4j::ClassArguments.validate_argument!(argument, direction)
|
|
61
|
+
|
|
62
|
+
instance_variable_set("@#{direction}", argument)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
self.instance_variable_get("@#{direction}")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
define_method("_#{direction}") { instance_variable_get "@#{direction}" }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def valid_class_argument?(class_argument)
|
|
72
|
+
[String, Symbol, FalseClass].include?(class_argument.class) ||
|
|
73
|
+
(class_argument.is_a?(Array) && class_argument.all? { |c| [String, Symbol].include?(c.class) })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
alias start_class from_class
|
|
77
|
+
alias end_class to_class
|
|
78
|
+
|
|
79
|
+
def load_entity(id)
|
|
80
|
+
Neo4j::Node.load(id)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def load_nodes(from_node = nil, to_node = nil)
|
|
87
|
+
@from_node = RelatedNode.new(from_node)
|
|
88
|
+
@to_node = RelatedNode.new(to_node)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def inspect_attributes
|
|
92
|
+
attributes.to_a
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module Neo4j::ActiveRel
|
|
2
|
+
module Query
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
class RecordNotFound < Neo4j::RecordNotFound; end
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
# Returns the object with the specified neo4j id.
|
|
9
|
+
# @param [String,Integer] id of node to find
|
|
10
|
+
# @param [Neo4j::Session] session optional
|
|
11
|
+
def find(id, session = self.neo4j_session)
|
|
12
|
+
fail "Unknown argument #{id.class} in find method (expected String or Integer)" if !(id.is_a?(String) || id.is_a?(Integer))
|
|
13
|
+
find_by_id(id, session)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Loads the relationship using its neo_id.
|
|
17
|
+
def find_by_id(key, session = nil)
|
|
18
|
+
options = session ? {session: session} : {}
|
|
19
|
+
query ||= Neo4j::ActiveBase.new_query(options)
|
|
20
|
+
result = query.match('()-[r]-()').where('ID(r)' => key.to_i).limit(1).return(:r).first
|
|
21
|
+
fail RecordNotFound.new("Couldn't find #{name} with 'id'=#{key.inspect}", name, key) if result.blank?
|
|
22
|
+
result.r
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Performs a very basic match on the relationship.
|
|
26
|
+
# This is not executed lazily, it will immediately return matching objects.
|
|
27
|
+
# To use a string, prefix the property with "r1"
|
|
28
|
+
# @example Match with a string
|
|
29
|
+
# MyRelClass.where('r1.grade > r1')
|
|
30
|
+
def where(args = {})
|
|
31
|
+
where_query.where(where_string(args)).pluck(:r1)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Performs a basic match on the relationship, returning all results.
|
|
35
|
+
# This is not executed lazily, it will immediately return matching objects.
|
|
36
|
+
def all
|
|
37
|
+
all_query.pluck(:r1)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def first
|
|
41
|
+
all_query.limit(1).order('ID(r1)').pluck(:r1).first
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def last
|
|
45
|
+
all_query.limit(1).order('ID(r1) DESC').pluck(:r1).first
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def deprecation_warning!
|
|
51
|
+
ActiveSupport::Deprecation.warn 'The Neo4j::ActiveRel::Query module has been deprecated and will be removed in a future version of the gem.', caller
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def where_query
|
|
55
|
+
deprecation_warning!
|
|
56
|
+
Neo4j::ActiveBase.new_query.match("#{cypher_string(:outbound)}-[r1:`#{self._type}`]->#{cypher_string(:inbound)}")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def all_query
|
|
60
|
+
deprecation_warning!
|
|
61
|
+
Neo4j::ActiveBase.new_query.match("#{cypher_string}-[r1:`#{self._type}`]->#{cypher_string(:inbound)}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def cypher_string(dir = :outbound)
|
|
65
|
+
case dir
|
|
66
|
+
when :outbound
|
|
67
|
+
identifier = '(n1'
|
|
68
|
+
identifier + (_from_class == :any ? ')' : cypher_label(:outbound))
|
|
69
|
+
when :inbound
|
|
70
|
+
identifier = '(n2'
|
|
71
|
+
identifier + (_to_class == :any ? ')' : cypher_label(:inbound))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def cypher_label(dir = :outbound)
|
|
76
|
+
target_class = dir == :outbound ? as_constant(_from_class) : as_constant(_to_class)
|
|
77
|
+
":`#{target_class.mapped_label_name}`)"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def as_constant(given_class)
|
|
81
|
+
case given_class
|
|
82
|
+
when String, Symbol
|
|
83
|
+
given_class.to_s.constantize
|
|
84
|
+
when Array
|
|
85
|
+
fail "ActiveRel query methods are being deprecated and do not support Array (from|to)_class options. Current value: #{given_class}"
|
|
86
|
+
else
|
|
87
|
+
given_class
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def where_string(args)
|
|
92
|
+
case args
|
|
93
|
+
when Hash
|
|
94
|
+
args.map { |k, v| v.is_a?(Integer) ? "r1.#{k} = #{v}" : "r1.#{k} = '#{v}'" }.join(', ')
|
|
95
|
+
else
|
|
96
|
+
args
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|