activegraph 11.0.0.beta.1-java
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 +2016 -0
- data/CONTRIBUTORS +12 -0
- data/Gemfile +24 -0
- data/README.md +111 -0
- data/activegraph.gemspec +52 -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 +35 -0
- data/lib/active_graph.rb +123 -0
- data/lib/active_graph/ansi.rb +14 -0
- data/lib/active_graph/attribute_set.rb +32 -0
- data/lib/active_graph/base.rb +77 -0
- data/lib/active_graph/class_arguments.rb +39 -0
- data/lib/active_graph/config.rb +135 -0
- data/lib/active_graph/core.rb +14 -0
- data/lib/active_graph/core/connection_failed_error.rb +6 -0
- data/lib/active_graph/core/cypher_error.rb +37 -0
- data/lib/active_graph/core/entity.rb +11 -0
- data/lib/active_graph/core/instrumentable.rb +37 -0
- data/lib/active_graph/core/label.rb +135 -0
- data/lib/active_graph/core/logging.rb +44 -0
- data/lib/active_graph/core/node.rb +15 -0
- data/lib/active_graph/core/querable.rb +41 -0
- data/lib/active_graph/core/query.rb +485 -0
- data/lib/active_graph/core/query_builder.rb +18 -0
- data/lib/active_graph/core/query_clauses.rb +727 -0
- data/lib/active_graph/core/query_ext.rb +24 -0
- data/lib/active_graph/core/query_find_in_batches.rb +46 -0
- data/lib/active_graph/core/record.rb +51 -0
- data/lib/active_graph/core/result.rb +31 -0
- data/lib/active_graph/core/schema.rb +65 -0
- data/lib/active_graph/core/schema_errors.rb +12 -0
- data/lib/active_graph/core/wrappable.rb +30 -0
- data/lib/active_graph/errors.rb +59 -0
- data/lib/active_graph/lazy_attribute_hash.rb +38 -0
- data/lib/active_graph/migration.rb +148 -0
- data/lib/active_graph/migrations.rb +27 -0
- data/lib/active_graph/migrations/base.rb +77 -0
- data/lib/active_graph/migrations/check_pending.rb +20 -0
- data/lib/active_graph/migrations/helpers.rb +105 -0
- data/lib/active_graph/migrations/helpers/id_property.rb +72 -0
- data/lib/active_graph/migrations/helpers/relationships.rb +66 -0
- data/lib/active_graph/migrations/helpers/schema.rb +63 -0
- data/lib/active_graph/migrations/migration_file.rb +24 -0
- data/lib/active_graph/migrations/runner.rb +195 -0
- data/lib/active_graph/migrations/schema.rb +64 -0
- data/lib/active_graph/migrations/schema_migration.rb +14 -0
- data/lib/active_graph/model_schema.rb +139 -0
- data/lib/active_graph/node.rb +110 -0
- data/lib/active_graph/node/callbacks.rb +8 -0
- data/lib/active_graph/node/dependent.rb +11 -0
- data/lib/active_graph/node/dependent/association_methods.rb +49 -0
- data/lib/active_graph/node/dependent/query_proxy_methods.rb +52 -0
- data/lib/active_graph/node/dependent_callbacks.rb +31 -0
- data/lib/active_graph/node/enum.rb +26 -0
- data/lib/active_graph/node/has_n.rb +602 -0
- data/lib/active_graph/node/has_n/association.rb +278 -0
- data/lib/active_graph/node/has_n/association/rel_factory.rb +61 -0
- data/lib/active_graph/node/has_n/association/rel_wrapper.rb +23 -0
- data/lib/active_graph/node/has_n/association_cypher_methods.rb +108 -0
- data/lib/active_graph/node/id_property.rb +224 -0
- data/lib/active_graph/node/id_property/accessor.rb +62 -0
- data/lib/active_graph/node/initialize.rb +21 -0
- data/lib/active_graph/node/labels.rb +207 -0
- data/lib/active_graph/node/labels/index.rb +37 -0
- data/lib/active_graph/node/labels/reloading.rb +21 -0
- data/lib/active_graph/node/node_list_formatter.rb +13 -0
- data/lib/active_graph/node/node_wrapper.rb +54 -0
- data/lib/active_graph/node/orm_adapter.rb +82 -0
- data/lib/active_graph/node/persistence.rb +186 -0
- data/lib/active_graph/node/property.rb +60 -0
- data/lib/active_graph/node/query.rb +76 -0
- data/lib/active_graph/node/query/query_proxy.rb +367 -0
- data/lib/active_graph/node/query/query_proxy_eager_loading.rb +177 -0
- data/lib/active_graph/node/query/query_proxy_eager_loading/association_tree.rb +75 -0
- data/lib/active_graph/node/query/query_proxy_enumerable.rb +110 -0
- data/lib/active_graph/node/query/query_proxy_find_in_batches.rb +19 -0
- data/lib/active_graph/node/query/query_proxy_link.rb +139 -0
- data/lib/active_graph/node/query/query_proxy_methods.rb +303 -0
- data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +99 -0
- data/lib/active_graph/node/query_methods.rb +68 -0
- data/lib/active_graph/node/reflection.rb +86 -0
- data/lib/active_graph/node/rels.rb +11 -0
- data/lib/active_graph/node/scope.rb +166 -0
- data/lib/active_graph/node/unpersisted.rb +48 -0
- data/lib/active_graph/node/validations.rb +59 -0
- data/lib/active_graph/paginated.rb +27 -0
- data/lib/active_graph/railtie.rb +108 -0
- data/lib/active_graph/relationship.rb +68 -0
- data/lib/active_graph/relationship/callbacks.rb +21 -0
- data/lib/active_graph/relationship/initialize.rb +28 -0
- data/lib/active_graph/relationship/persistence.rb +133 -0
- data/lib/active_graph/relationship/persistence/query_factory.rb +95 -0
- data/lib/active_graph/relationship/property.rb +92 -0
- data/lib/active_graph/relationship/query.rb +99 -0
- data/lib/active_graph/relationship/rel_wrapper.rb +31 -0
- data/lib/active_graph/relationship/related_node.rb +87 -0
- data/lib/active_graph/relationship/types.rb +80 -0
- data/lib/active_graph/relationship/validations.rb +8 -0
- data/lib/active_graph/schema/operation.rb +102 -0
- data/lib/active_graph/shared.rb +48 -0
- data/lib/active_graph/shared/attributes.rb +217 -0
- data/lib/active_graph/shared/callbacks.rb +66 -0
- data/lib/active_graph/shared/cypher.rb +37 -0
- data/lib/active_graph/shared/declared_properties.rb +204 -0
- data/lib/active_graph/shared/declared_property.rb +109 -0
- data/lib/active_graph/shared/declared_property/index.rb +37 -0
- data/lib/active_graph/shared/enum.rb +167 -0
- data/lib/active_graph/shared/filtered_hash.rb +79 -0
- data/lib/active_graph/shared/identity.rb +34 -0
- data/lib/active_graph/shared/initialize.rb +65 -0
- data/lib/active_graph/shared/marshal.rb +23 -0
- data/lib/active_graph/shared/mass_assignment.rb +63 -0
- data/lib/active_graph/shared/permitted_attributes.rb +28 -0
- data/lib/active_graph/shared/persistence.rb +272 -0
- data/lib/active_graph/shared/property.rb +249 -0
- data/lib/active_graph/shared/query_factory.rb +122 -0
- data/lib/active_graph/shared/rel_type_converters.rb +43 -0
- data/lib/active_graph/shared/serialized_properties.rb +30 -0
- data/lib/active_graph/shared/type_converters.rb +439 -0
- data/lib/active_graph/shared/typecasted_attributes.rb +99 -0
- data/lib/active_graph/shared/typecaster.rb +53 -0
- data/lib/active_graph/shared/validations.rb +44 -0
- data/lib/active_graph/tasks/migration.rake +204 -0
- data/lib/active_graph/timestamps.rb +11 -0
- data/lib/active_graph/timestamps/created.rb +9 -0
- data/lib/active_graph/timestamps/updated.rb +9 -0
- data/lib/active_graph/transaction.rb +22 -0
- data/lib/active_graph/transactions.rb +57 -0
- data/lib/active_graph/type_converters.rb +7 -0
- data/lib/active_graph/undeclared_properties.rb +53 -0
- data/lib/active_graph/version.rb +3 -0
- data/lib/active_graph/wrapper.rb +4 -0
- data/lib/rails/generators/active_graph/migration/migration_generator.rb +16 -0
- data/lib/rails/generators/active_graph/migration/templates/migration.erb +9 -0
- data/lib/rails/generators/active_graph/model/model_generator.rb +89 -0
- data/lib/rails/generators/active_graph/model/templates/migration.erb +11 -0
- data/lib/rails/generators/active_graph/model/templates/model.erb +15 -0
- data/lib/rails/generators/active_graph/upgrade_v8/templates/migration.erb +17 -0
- data/lib/rails/generators/active_graph/upgrade_v8/upgrade_v8_generator.rb +34 -0
- data/lib/rails/generators/active_graph_generator.rb +121 -0
- metadata +423 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module ActiveGraph::Node::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 ActiveGraph::Node::Property::ClassMethods#property.
|
|
13
|
+
#
|
|
14
|
+
# @param [Symbol] property the property we want a Neo4j index on
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# class Person
|
|
18
|
+
# include ActiveGraph::Node
|
|
19
|
+
# property :name
|
|
20
|
+
# index :name
|
|
21
|
+
# end
|
|
22
|
+
def index(property)
|
|
23
|
+
return if ActiveGraph::ModelSchema.defined_constraint?(self, property)
|
|
24
|
+
|
|
25
|
+
ActiveGraph::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
|
+
ActiveGraph::ModelSchema.add_defined_constraint(self, property)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ActiveGraph::Node::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 ActiveGraph::Node
|
|
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 'active_graph/core/node'
|
|
3
|
+
|
|
4
|
+
wrapping_proc = proc do |node|
|
|
5
|
+
found_class = ActiveGraph::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.properties)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
Neo4j::Driver::Types::Node.wrapper_callback(wrapping_proc)
|
|
13
|
+
|
|
14
|
+
module ActiveGraph
|
|
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
|
+
ActiveGraph::Node::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
|
+
ActiveGraph::Config.association_model_namespace_string
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'orm_adapter'
|
|
2
|
+
|
|
3
|
+
module ActiveGraph
|
|
4
|
+
module Node
|
|
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,186 @@
|
|
|
1
|
+
module ActiveGraph::Node
|
|
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 ActiveGraph::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] attribute name of the attribute to increment
|
|
34
|
+
# @param [Integer, Float] by 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 ActiveGraph::Rails::Validations#save)
|
|
45
|
+
# @return nil
|
|
46
|
+
# @see #save
|
|
47
|
+
# @see ActiveGraph::Rails::Validations ActiveGraph::Rails::Validations - for the :validate parameter
|
|
48
|
+
# @see ActiveGraph::Rails::Callbacks ActiveGraph::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.properties)
|
|
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 [ActiveGraph::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: false).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.
|
|
97
|
+
def cascade_save
|
|
98
|
+
ActiveGraph::Base.transaction do
|
|
99
|
+
yield.tap { process_unpersisted_nodes! }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
module ClassMethods
|
|
104
|
+
# Creates and saves a new node
|
|
105
|
+
# @param [Hash] props the properties the new node should have
|
|
106
|
+
def create(props = {})
|
|
107
|
+
new(props).tap do |obj|
|
|
108
|
+
yield obj if block_given?
|
|
109
|
+
obj.save
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Same as #create, but raises an error if there is a problem during save.
|
|
114
|
+
def create!(props = {})
|
|
115
|
+
new(props).tap do |o|
|
|
116
|
+
yield o if block_given?
|
|
117
|
+
o.save!
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def merge(match_attributes, optional_attrs = {})
|
|
122
|
+
options = [:on_create, :on_match, :set]
|
|
123
|
+
optional_attrs.assert_valid_keys(*options)
|
|
124
|
+
|
|
125
|
+
optional_attrs.default = {}
|
|
126
|
+
on_create_attrs, on_match_attrs, set_attrs = optional_attrs.values_at(*options)
|
|
127
|
+
|
|
128
|
+
new_query.merge(n: {self.mapped_label_names => match_attributes})
|
|
129
|
+
.on_create_set(on_create_clause(on_create_attrs))
|
|
130
|
+
.on_match_set(on_match_clause(on_match_attrs))
|
|
131
|
+
.break.set(n: set_attrs)
|
|
132
|
+
.pluck(:n).first
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def find_or_create(find_attributes, set_attributes = {})
|
|
136
|
+
on_create_attributes = set_attributes.reverse_merge(find_attributes.merge(self.new(find_attributes).props_for_create))
|
|
137
|
+
|
|
138
|
+
new_query.merge(n: {self.mapped_label_names => find_attributes})
|
|
139
|
+
.on_create_set(n: on_create_attributes)
|
|
140
|
+
.pluck(:n).first
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Finds the first node with the given attributes, or calls create if none found
|
|
144
|
+
def find_or_create_by(attributes, &block)
|
|
145
|
+
find_by(attributes) || create(attributes, &block)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Same as #find_or_create_by, but calls #create! so it raises an error if there is a problem during save.
|
|
149
|
+
def find_or_create_by!(attributes, &block)
|
|
150
|
+
find_by(attributes) || create!(attributes, &block)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def find_or_initialize_by(attributes)
|
|
154
|
+
find_by(attributes) || new(attributes).tap { |o| yield(o) if block_given? }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def load_entity(id)
|
|
158
|
+
query = query_base_for(id, :n).return(:n)
|
|
159
|
+
result = neo4j_query(query).first
|
|
160
|
+
result && result[:n]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def query_base_for(neo_id, var = :n)
|
|
164
|
+
ActiveGraph::Base.new_query.match(var).where(var => {neo_id: neo_id})
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def on_create_clause(clause)
|
|
170
|
+
if clause.is_a?(Hash)
|
|
171
|
+
{n: clause.merge(self.new(clause).props_for_create)}
|
|
172
|
+
else
|
|
173
|
+
clause
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def on_match_clause(clause)
|
|
178
|
+
if clause.is_a?(Hash)
|
|
179
|
+
{n: clause.merge(attributes_nil_hash.key?('updated_at') ? {updated_at: Time.new.to_i} : {})}
|
|
180
|
+
else
|
|
181
|
+
clause
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module ActiveGraph::Node
|
|
2
|
+
module Property
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
include ActiveGraph::Shared::Property
|
|
5
|
+
|
|
6
|
+
def initialize(attributes = nil)
|
|
7
|
+
super(attributes)
|
|
8
|
+
@attributes ||= ActiveGraph::AttributeSet.new(self.class.attributes_nil_hash, self.class.attributes.keys)
|
|
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
|