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,99 @@
|
|
|
1
|
+
module ActiveGraph
|
|
2
|
+
module Node
|
|
3
|
+
module Query
|
|
4
|
+
module QueryProxyMethodsOfMassUpdating
|
|
5
|
+
# Updates some attributes of a group of nodes within a QP chain.
|
|
6
|
+
# The optional argument makes sense only of `updates` is a string.
|
|
7
|
+
# @param [Hash,String] updates An hash or a string of parameters to be updated.
|
|
8
|
+
# @param [Hash] params An hash of parameters for the update string. It's ignored if `updates` is an Hash.
|
|
9
|
+
def update_all(updates, params = {})
|
|
10
|
+
# Move this to Node module?
|
|
11
|
+
update_all_with_query(identity, updates, params)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Updates some attributes of a group of relationships within a QP chain.
|
|
15
|
+
# The optional argument makes sense only of `updates` is a string.
|
|
16
|
+
# @param [Hash,String] updates An hash or a string of parameters to be updated.
|
|
17
|
+
# @param [Hash] params An hash of parameters for the update string. It's ignored if `updates` is an Hash.
|
|
18
|
+
def update_all_rels(updates, params = {})
|
|
19
|
+
fail 'Cannot update rels without a relationship variable.' unless @rel_var
|
|
20
|
+
update_all_with_query(@rel_var, updates, params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Deletes a group of nodes and relationships within a QP chain. When identifier is omitted, it will remove the last link in the chain.
|
|
24
|
+
# The optional argument must be a node identifier. A relationship identifier will result in a Cypher Error
|
|
25
|
+
# @param identifier [String,Symbol] the optional identifier of the link in the chain to delete.
|
|
26
|
+
def delete_all(identifier = nil)
|
|
27
|
+
query_with_target(identifier) do |target|
|
|
28
|
+
begin
|
|
29
|
+
self.query.with(target).optional_match("(#{target})-[#{target}_rel]-()").delete("#{target}, #{target}_rel").exec
|
|
30
|
+
rescue Neo4j::Driver::Exceptions::ClientException # <=- Seems hacky
|
|
31
|
+
self.query.delete(target).exec
|
|
32
|
+
end
|
|
33
|
+
clear_source_object_cache
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Deletes the relationship between a node and its last link in the QueryProxy chain. Executed in the database, callbacks will not run.
|
|
38
|
+
def delete(node)
|
|
39
|
+
self.match_to(node).query.delete(rel_var).exec
|
|
40
|
+
clear_source_object_cache
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Deletes the relationships between all nodes for the last step in the QueryProxy chain. Executed in the database, callbacks will not be run.
|
|
44
|
+
def delete_all_rels
|
|
45
|
+
return unless start_object && start_object._persisted_obj
|
|
46
|
+
self.query.delete(rel_var).exec
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Deletes the relationships between all nodes for the last step in the QueryProxy chain and replaces them with relationships to the given nodes.
|
|
50
|
+
# Executed in the database, callbacks will not be run.
|
|
51
|
+
def replace_with(node_or_nodes)
|
|
52
|
+
node_or_nodes = Array(node_or_nodes).map { |arg| arg.is_a?(ActiveGraph::Node) ? arg : @model.find(arg) }
|
|
53
|
+
original_ids = self.pluck(:id)
|
|
54
|
+
delete_rels_for_nodes(original_ids, node_or_nodes.collect(&:id))
|
|
55
|
+
add_rels(node_or_nodes, original_ids)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def add_rels(node_or_nodes, original_ids)
|
|
59
|
+
node_or_nodes.map do |obj|
|
|
60
|
+
obj if original_ids.include?(obj.id) || _create_relation_or_defer(obj)
|
|
61
|
+
end.compact
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def delete_rels_for_nodes(original_ids, new_ids)
|
|
65
|
+
ids = original_ids.select { |id| !new_ids.include?(id) }
|
|
66
|
+
return unless ids.present?
|
|
67
|
+
if association.dependent
|
|
68
|
+
start_object.public_send("dependent_#{association.dependent}_callback", association, ids)
|
|
69
|
+
else
|
|
70
|
+
self.where(id: ids).delete_all_rels
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns all relationships between a node and its last link in the QueryProxy chain, destroys them in Ruby. Callbacks will be run.
|
|
75
|
+
def destroy(node)
|
|
76
|
+
self.rels_to(node).map!(&:destroy)
|
|
77
|
+
clear_source_object_cache
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def update_all_with_query(var_name, updates, params)
|
|
83
|
+
query = all.query
|
|
84
|
+
|
|
85
|
+
case updates
|
|
86
|
+
when Hash then query.set(var_name => updates).pluck("count(#{var_name})").first
|
|
87
|
+
when String then query.set(updates).params(params).pluck("count(#{var_name})").first
|
|
88
|
+
else
|
|
89
|
+
fail ArgumentError, "Invalid parameter type #{updates.class} for `updates`."
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def clear_source_object_cache
|
|
94
|
+
self.source_object.clear_association_cache if self.source_object.respond_to?(:clear_association_cache)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module ActiveGraph
|
|
2
|
+
module Node
|
|
3
|
+
module QueryMethods
|
|
4
|
+
def exists?(node_condition = nil)
|
|
5
|
+
unless [Integer, String, Hash, NilClass].any? { |c| node_condition.is_a?(c) }
|
|
6
|
+
fail(ActiveGraph::InvalidParameterError, ':exists? only accepts ids or conditions')
|
|
7
|
+
end
|
|
8
|
+
query_start = exists_query_start(node_condition)
|
|
9
|
+
start_q = query_start.respond_to?(:query_as) ? query_start.query_as(:n) : query_start
|
|
10
|
+
result = start_q.return('ID(n) AS proof_of_life LIMIT 1').first
|
|
11
|
+
!!result
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns the first node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs.
|
|
15
|
+
def first
|
|
16
|
+
self.query_as(:n).limit(1).order(n: primary_key).pluck(:n).first
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns the last node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs.
|
|
20
|
+
def last
|
|
21
|
+
self.query_as(:n).limit(1).order(n: {primary_key => :desc}).pluck(:n).first
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Integer] number of nodes of this class
|
|
25
|
+
def count(distinct = nil)
|
|
26
|
+
fail(ActiveGraph::InvalidParameterError, ':count accepts the `:distinct` symbol or nil as a parameter') unless distinct.nil? || distinct == :distinct
|
|
27
|
+
q = distinct.nil? ? 'n' : 'DISTINCT n'
|
|
28
|
+
self.query_as(:n).return("count(#{q}) AS count").first[:count]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
alias size count
|
|
32
|
+
alias length count
|
|
33
|
+
|
|
34
|
+
def empty?
|
|
35
|
+
!self.all.exists?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
alias blank? empty?
|
|
39
|
+
|
|
40
|
+
def find_in_batches(options = {})
|
|
41
|
+
self.query_as(:n).return(:n).find_in_batches(:n, primary_key, options) do |batch|
|
|
42
|
+
yield batch.map { |record| record[:n] }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def find_each(options = {})
|
|
47
|
+
self.query_as(:n).return(:n).find_each(:n, primary_key, options) do |batch|
|
|
48
|
+
yield batch[:n]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def exists_query_start(node_condition)
|
|
55
|
+
case node_condition
|
|
56
|
+
when Integer
|
|
57
|
+
self.query_as(:n).where('ID(n)' => node_condition)
|
|
58
|
+
when String
|
|
59
|
+
self.query_as(:n).where(n: {primary_key => node_condition})
|
|
60
|
+
when Hash
|
|
61
|
+
self.where(node_condition.keys.first => node_condition.values.first)
|
|
62
|
+
else
|
|
63
|
+
self.query_as(:n)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module ActiveGraph::Node
|
|
2
|
+
# A reflection contains information about an association.
|
|
3
|
+
# They are often used in connection with form builders to determine associated classes.
|
|
4
|
+
# This module contains methods related to the creation and retrieval of reflections.
|
|
5
|
+
module Reflection
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
class_attribute :reflections
|
|
10
|
+
self.reflections = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Adds methods to the class related to creating and retrieving reflections.
|
|
14
|
+
module ClassMethods
|
|
15
|
+
# @param macro [Symbol] the association type, :has_many or :has_one
|
|
16
|
+
# @param name [Symbol] the association name
|
|
17
|
+
# @param association_object [ActiveGraph::Node::HasN::Association] the association object created in the course of creating this reflection
|
|
18
|
+
def create_reflection(macro, name, association_object, model)
|
|
19
|
+
self.reflections = self.reflections.merge(name => AssociationReflection.new(macro, name, association_object))
|
|
20
|
+
association_object.add_destroy_callbacks(model)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private :create_reflection
|
|
24
|
+
# @param association [Symbol] an association declared on the model
|
|
25
|
+
# @return [ActiveGraph::Node::Reflection::AssociationReflection] of the given association
|
|
26
|
+
def reflect_on_association(association)
|
|
27
|
+
reflections[association.to_sym]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns an array containing one reflection for each association declared in the model.
|
|
31
|
+
def reflect_on_all_associations(macro = nil)
|
|
32
|
+
association_reflections = reflections.values
|
|
33
|
+
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The actual reflection object that contains information about the given association.
|
|
38
|
+
# These should never need to be created manually, they will always be created by declaring a :has_many or :has_one association on a model.
|
|
39
|
+
class AssociationReflection
|
|
40
|
+
# The name of the association
|
|
41
|
+
attr_reader :name
|
|
42
|
+
|
|
43
|
+
# The type of association
|
|
44
|
+
attr_reader :macro
|
|
45
|
+
|
|
46
|
+
# The association object referenced by this reflection
|
|
47
|
+
attr_reader :association
|
|
48
|
+
|
|
49
|
+
def initialize(macro, name, association)
|
|
50
|
+
@macro = macro
|
|
51
|
+
@name = name
|
|
52
|
+
@association = association
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the target model
|
|
56
|
+
def klass
|
|
57
|
+
@klass ||= class_name.constantize
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the name of the target model
|
|
61
|
+
def class_name
|
|
62
|
+
@class_name ||= association.target_class.name
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def rel_klass
|
|
66
|
+
@rel_klass ||= rel_class_name.constantize
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def rel_class_name
|
|
70
|
+
@rel_class_name ||= association.relationship_class.name.to_s
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def type
|
|
74
|
+
@type ||= association.relationship_type
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def collection?
|
|
78
|
+
macro == :has_many
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def validate?
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module ActiveGraph::Node
|
|
2
|
+
module Rels
|
|
3
|
+
extend Forwardable
|
|
4
|
+
def_delegators :_rels_delegator, :rel?, :rel, :rels, :node, :nodes, :create_rel
|
|
5
|
+
|
|
6
|
+
def _rels_delegator
|
|
7
|
+
fail "Can't access relationship on a non persisted node" unless _persisted_obj
|
|
8
|
+
_persisted_obj
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
require 'active_support/per_thread_registry'
|
|
2
|
+
|
|
3
|
+
module ActiveGraph::Node
|
|
4
|
+
module Scope
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
# Similar to ActiveRecord scope
|
|
9
|
+
#
|
|
10
|
+
# @example without argument
|
|
11
|
+
# class Person
|
|
12
|
+
# include ActiveGraph::Node
|
|
13
|
+
# property :name
|
|
14
|
+
# property :score
|
|
15
|
+
# has_many :out, :friends, type: :has_friend, model_class: self
|
|
16
|
+
# scope :top_students, -> { where(score: 42)}") }
|
|
17
|
+
# end
|
|
18
|
+
# Person.top_students.to_a
|
|
19
|
+
# a_person.friends.top_students.to_a
|
|
20
|
+
# a_person.friends.friends.top_students.to_a
|
|
21
|
+
# a_person.friends.top_students.friends.to_a
|
|
22
|
+
#
|
|
23
|
+
# @example Argument for scopes
|
|
24
|
+
# Person.scope :level, ->(num) { where(level_num: num)}
|
|
25
|
+
#
|
|
26
|
+
# @example Argument as a cypher identifier
|
|
27
|
+
# class Person
|
|
28
|
+
# include ActiveGraph::Node
|
|
29
|
+
# property :name
|
|
30
|
+
# property :score
|
|
31
|
+
# has_many :out, :friends, type: :has_friend, model_class: self
|
|
32
|
+
# scope :great_students, ->(identifier) { where("#{identifier}.score > 41") }
|
|
33
|
+
# end
|
|
34
|
+
# Person.as(:all_people).great_students(:all_people).to_a
|
|
35
|
+
#
|
|
36
|
+
# @see http://guides.rubyonrails.org/active_record_querying.html#scopes
|
|
37
|
+
def scope(name, proc)
|
|
38
|
+
scopes[name.to_sym] = proc
|
|
39
|
+
|
|
40
|
+
klass = class << self; self; end
|
|
41
|
+
klass.instance_eval do
|
|
42
|
+
define_method(name) do |*query_params|
|
|
43
|
+
eval_context = ScopeEvalContext.new(self, current_scope || self.query_proxy)
|
|
44
|
+
proc = full_scopes[name.to_sym]
|
|
45
|
+
_call_scope_context(eval_context, query_params, proc)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
define_method(name) do |*query_params|
|
|
50
|
+
as(:n).public_send(name, *query_params)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# rubocop:disable Naming/PredicateName
|
|
55
|
+
def has_scope?(name)
|
|
56
|
+
ActiveSupport::Deprecation.warn 'has_scope? is deprecated and may be removed from future releases, use scope? instead.', caller
|
|
57
|
+
|
|
58
|
+
scope?(name)
|
|
59
|
+
end
|
|
60
|
+
# rubocop:enable Naming/PredicateName
|
|
61
|
+
|
|
62
|
+
# @return [Boolean] true if model has access to scope with this name
|
|
63
|
+
def scope?(name)
|
|
64
|
+
full_scopes.key?(name.to_sym)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [Hash] of scopes assigned to this model. Keys are scope name, value is scope callable.
|
|
68
|
+
def scopes
|
|
69
|
+
@scopes ||= {}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Hash] of scopes available to this model. Keys are scope name, value is scope callable.
|
|
73
|
+
def full_scopes
|
|
74
|
+
self.ancestors.find_all { |a| a.respond_to?(:scopes) }.reverse.inject({}) do |scopes, a|
|
|
75
|
+
scopes.merge(a.scopes)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def _call_scope_context(eval_context, query_params, proc)
|
|
80
|
+
eval_context.instance_exec(*query_params.fill(nil, query_params.length..proc.arity - 1), &proc)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def current_scope #:nodoc:
|
|
84
|
+
ScopeRegistry.value_for(:current_scope, base_class.to_s)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def current_scope=(scope) #:nodoc:
|
|
88
|
+
ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def all(new_var = nil)
|
|
92
|
+
var = new_var || (current_scope ? current_scope.node_identity : :n)
|
|
93
|
+
if current_scope
|
|
94
|
+
current_scope.new_link(var)
|
|
95
|
+
else
|
|
96
|
+
self.as(var)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class ScopeEvalContext
|
|
102
|
+
def initialize(target, query_proxy)
|
|
103
|
+
@query_proxy = query_proxy
|
|
104
|
+
@target = target
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def identity
|
|
108
|
+
query_proxy_or_target.identity
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
ActiveGraph::Node::Query::QueryProxy::METHODS.each do |method|
|
|
112
|
+
define_method(method) do |*args|
|
|
113
|
+
@target.all.scoping do
|
|
114
|
+
query_proxy_or_target.public_send(method, *args)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# method_missing is not delegated to super class but to aggregated class
|
|
120
|
+
# rubocop:disable Style/MethodMissingSuper
|
|
121
|
+
def method_missing(name, *params, &block)
|
|
122
|
+
query_proxy_or_target.public_send(name, *params, &block)
|
|
123
|
+
end
|
|
124
|
+
# rubocop:enable Style/MethodMissingSuper
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def query_proxy_or_target
|
|
129
|
+
@query_proxy_or_target ||= @query_proxy || @target
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Stolen from ActiveRecord
|
|
135
|
+
# https://github.com/rails/rails/blob/08754f12e65a9ec79633a605e986d0f1ffa4b251/activerecord/lib/active_record/scoping.rb#L57
|
|
136
|
+
class ScopeRegistry # :nodoc:
|
|
137
|
+
extend ActiveSupport::PerThreadRegistry
|
|
138
|
+
|
|
139
|
+
VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
|
|
140
|
+
|
|
141
|
+
def initialize
|
|
142
|
+
@registry = Hash.new { |hash, key| hash[key] = {} }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Obtains the value for a given +scope_name+ and +variable_name+.
|
|
146
|
+
def value_for(scope_type, variable_name)
|
|
147
|
+
raise_invalid_scope_type!(scope_type)
|
|
148
|
+
@registry[scope_type][variable_name]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Sets the +value+ for a given +scope_type+ and +variable_name+.
|
|
152
|
+
def set_value_for(scope_type, variable_name, value)
|
|
153
|
+
raise_invalid_scope_type!(scope_type)
|
|
154
|
+
@registry[scope_type][variable_name] = value
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def raise_invalid_scope_type!(scope_type)
|
|
160
|
+
return if VALID_SCOPE_TYPES.include?(scope_type)
|
|
161
|
+
|
|
162
|
+
fail ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module ActiveGraph
|
|
2
|
+
module Node
|
|
3
|
+
module Unpersisted
|
|
4
|
+
# The values in this Hash are returned and used outside by reference
|
|
5
|
+
# so any modifications to the Array should be in-place
|
|
6
|
+
def deferred_create_cache
|
|
7
|
+
@deferred_create_cache ||= {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def defer_create(association_name, object, options = {})
|
|
11
|
+
clear_deferred_nodes_for_association(association_name) if options[:clear]
|
|
12
|
+
|
|
13
|
+
deferred_nodes_for_association(association_name).concat(Array(object))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def deferred_nodes_for_association(association_name)
|
|
17
|
+
deferred_create_cache[association_name.to_sym] ||= []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def pending_deferred_creations?
|
|
21
|
+
!deferred_create_cache.values.all?(&:empty?)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear_deferred_nodes_for_association(association_name)
|
|
25
|
+
deferred_nodes_for_association(association_name.to_sym).clear
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def process_unpersisted_nodes!
|
|
31
|
+
deferred_create_cache.dup.each do |association_name, nodes|
|
|
32
|
+
association_proxy = association_proxy(association_name)
|
|
33
|
+
|
|
34
|
+
nodes.each do |node|
|
|
35
|
+
if node.respond_to?(:changed?)
|
|
36
|
+
node.save if node.changed? || !node.persisted?
|
|
37
|
+
fail "Unable to defer node persistence, could not save #{node.inspect}" unless node.persisted?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
association_proxy << node
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@deferred_create_cache = {}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|