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.
Files changed (144) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2016 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +111 -0
  6. data/activegraph.gemspec +52 -0
  7. data/bin/rake +17 -0
  8. data/config/locales/en.yml +5 -0
  9. data/config/neo4j/add_classnames.yml +1 -0
  10. data/config/neo4j/config.yml +35 -0
  11. data/lib/active_graph.rb +123 -0
  12. data/lib/active_graph/ansi.rb +14 -0
  13. data/lib/active_graph/attribute_set.rb +32 -0
  14. data/lib/active_graph/base.rb +77 -0
  15. data/lib/active_graph/class_arguments.rb +39 -0
  16. data/lib/active_graph/config.rb +135 -0
  17. data/lib/active_graph/core.rb +14 -0
  18. data/lib/active_graph/core/connection_failed_error.rb +6 -0
  19. data/lib/active_graph/core/cypher_error.rb +37 -0
  20. data/lib/active_graph/core/entity.rb +11 -0
  21. data/lib/active_graph/core/instrumentable.rb +37 -0
  22. data/lib/active_graph/core/label.rb +135 -0
  23. data/lib/active_graph/core/logging.rb +44 -0
  24. data/lib/active_graph/core/node.rb +15 -0
  25. data/lib/active_graph/core/querable.rb +41 -0
  26. data/lib/active_graph/core/query.rb +485 -0
  27. data/lib/active_graph/core/query_builder.rb +18 -0
  28. data/lib/active_graph/core/query_clauses.rb +727 -0
  29. data/lib/active_graph/core/query_ext.rb +24 -0
  30. data/lib/active_graph/core/query_find_in_batches.rb +46 -0
  31. data/lib/active_graph/core/record.rb +51 -0
  32. data/lib/active_graph/core/result.rb +31 -0
  33. data/lib/active_graph/core/schema.rb +65 -0
  34. data/lib/active_graph/core/schema_errors.rb +12 -0
  35. data/lib/active_graph/core/wrappable.rb +30 -0
  36. data/lib/active_graph/errors.rb +59 -0
  37. data/lib/active_graph/lazy_attribute_hash.rb +38 -0
  38. data/lib/active_graph/migration.rb +148 -0
  39. data/lib/active_graph/migrations.rb +27 -0
  40. data/lib/active_graph/migrations/base.rb +77 -0
  41. data/lib/active_graph/migrations/check_pending.rb +20 -0
  42. data/lib/active_graph/migrations/helpers.rb +105 -0
  43. data/lib/active_graph/migrations/helpers/id_property.rb +72 -0
  44. data/lib/active_graph/migrations/helpers/relationships.rb +66 -0
  45. data/lib/active_graph/migrations/helpers/schema.rb +63 -0
  46. data/lib/active_graph/migrations/migration_file.rb +24 -0
  47. data/lib/active_graph/migrations/runner.rb +195 -0
  48. data/lib/active_graph/migrations/schema.rb +64 -0
  49. data/lib/active_graph/migrations/schema_migration.rb +14 -0
  50. data/lib/active_graph/model_schema.rb +139 -0
  51. data/lib/active_graph/node.rb +110 -0
  52. data/lib/active_graph/node/callbacks.rb +8 -0
  53. data/lib/active_graph/node/dependent.rb +11 -0
  54. data/lib/active_graph/node/dependent/association_methods.rb +49 -0
  55. data/lib/active_graph/node/dependent/query_proxy_methods.rb +52 -0
  56. data/lib/active_graph/node/dependent_callbacks.rb +31 -0
  57. data/lib/active_graph/node/enum.rb +26 -0
  58. data/lib/active_graph/node/has_n.rb +602 -0
  59. data/lib/active_graph/node/has_n/association.rb +278 -0
  60. data/lib/active_graph/node/has_n/association/rel_factory.rb +61 -0
  61. data/lib/active_graph/node/has_n/association/rel_wrapper.rb +23 -0
  62. data/lib/active_graph/node/has_n/association_cypher_methods.rb +108 -0
  63. data/lib/active_graph/node/id_property.rb +224 -0
  64. data/lib/active_graph/node/id_property/accessor.rb +62 -0
  65. data/lib/active_graph/node/initialize.rb +21 -0
  66. data/lib/active_graph/node/labels.rb +207 -0
  67. data/lib/active_graph/node/labels/index.rb +37 -0
  68. data/lib/active_graph/node/labels/reloading.rb +21 -0
  69. data/lib/active_graph/node/node_list_formatter.rb +13 -0
  70. data/lib/active_graph/node/node_wrapper.rb +54 -0
  71. data/lib/active_graph/node/orm_adapter.rb +82 -0
  72. data/lib/active_graph/node/persistence.rb +186 -0
  73. data/lib/active_graph/node/property.rb +60 -0
  74. data/lib/active_graph/node/query.rb +76 -0
  75. data/lib/active_graph/node/query/query_proxy.rb +367 -0
  76. data/lib/active_graph/node/query/query_proxy_eager_loading.rb +177 -0
  77. data/lib/active_graph/node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  78. data/lib/active_graph/node/query/query_proxy_enumerable.rb +110 -0
  79. data/lib/active_graph/node/query/query_proxy_find_in_batches.rb +19 -0
  80. data/lib/active_graph/node/query/query_proxy_link.rb +139 -0
  81. data/lib/active_graph/node/query/query_proxy_methods.rb +303 -0
  82. data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +99 -0
  83. data/lib/active_graph/node/query_methods.rb +68 -0
  84. data/lib/active_graph/node/reflection.rb +86 -0
  85. data/lib/active_graph/node/rels.rb +11 -0
  86. data/lib/active_graph/node/scope.rb +166 -0
  87. data/lib/active_graph/node/unpersisted.rb +48 -0
  88. data/lib/active_graph/node/validations.rb +59 -0
  89. data/lib/active_graph/paginated.rb +27 -0
  90. data/lib/active_graph/railtie.rb +108 -0
  91. data/lib/active_graph/relationship.rb +68 -0
  92. data/lib/active_graph/relationship/callbacks.rb +21 -0
  93. data/lib/active_graph/relationship/initialize.rb +28 -0
  94. data/lib/active_graph/relationship/persistence.rb +133 -0
  95. data/lib/active_graph/relationship/persistence/query_factory.rb +95 -0
  96. data/lib/active_graph/relationship/property.rb +92 -0
  97. data/lib/active_graph/relationship/query.rb +99 -0
  98. data/lib/active_graph/relationship/rel_wrapper.rb +31 -0
  99. data/lib/active_graph/relationship/related_node.rb +87 -0
  100. data/lib/active_graph/relationship/types.rb +80 -0
  101. data/lib/active_graph/relationship/validations.rb +8 -0
  102. data/lib/active_graph/schema/operation.rb +102 -0
  103. data/lib/active_graph/shared.rb +48 -0
  104. data/lib/active_graph/shared/attributes.rb +217 -0
  105. data/lib/active_graph/shared/callbacks.rb +66 -0
  106. data/lib/active_graph/shared/cypher.rb +37 -0
  107. data/lib/active_graph/shared/declared_properties.rb +204 -0
  108. data/lib/active_graph/shared/declared_property.rb +109 -0
  109. data/lib/active_graph/shared/declared_property/index.rb +37 -0
  110. data/lib/active_graph/shared/enum.rb +167 -0
  111. data/lib/active_graph/shared/filtered_hash.rb +79 -0
  112. data/lib/active_graph/shared/identity.rb +34 -0
  113. data/lib/active_graph/shared/initialize.rb +65 -0
  114. data/lib/active_graph/shared/marshal.rb +23 -0
  115. data/lib/active_graph/shared/mass_assignment.rb +63 -0
  116. data/lib/active_graph/shared/permitted_attributes.rb +28 -0
  117. data/lib/active_graph/shared/persistence.rb +272 -0
  118. data/lib/active_graph/shared/property.rb +249 -0
  119. data/lib/active_graph/shared/query_factory.rb +122 -0
  120. data/lib/active_graph/shared/rel_type_converters.rb +43 -0
  121. data/lib/active_graph/shared/serialized_properties.rb +30 -0
  122. data/lib/active_graph/shared/type_converters.rb +439 -0
  123. data/lib/active_graph/shared/typecasted_attributes.rb +99 -0
  124. data/lib/active_graph/shared/typecaster.rb +53 -0
  125. data/lib/active_graph/shared/validations.rb +44 -0
  126. data/lib/active_graph/tasks/migration.rake +204 -0
  127. data/lib/active_graph/timestamps.rb +11 -0
  128. data/lib/active_graph/timestamps/created.rb +9 -0
  129. data/lib/active_graph/timestamps/updated.rb +9 -0
  130. data/lib/active_graph/transaction.rb +22 -0
  131. data/lib/active_graph/transactions.rb +57 -0
  132. data/lib/active_graph/type_converters.rb +7 -0
  133. data/lib/active_graph/undeclared_properties.rb +53 -0
  134. data/lib/active_graph/version.rb +3 -0
  135. data/lib/active_graph/wrapper.rb +4 -0
  136. data/lib/rails/generators/active_graph/migration/migration_generator.rb +16 -0
  137. data/lib/rails/generators/active_graph/migration/templates/migration.erb +9 -0
  138. data/lib/rails/generators/active_graph/model/model_generator.rb +89 -0
  139. data/lib/rails/generators/active_graph/model/templates/migration.erb +11 -0
  140. data/lib/rails/generators/active_graph/model/templates/model.erb +15 -0
  141. data/lib/rails/generators/active_graph/upgrade_v8/templates/migration.erb +17 -0
  142. data/lib/rails/generators/active_graph/upgrade_v8/upgrade_v8_generator.rb +34 -0
  143. data/lib/rails/generators/active_graph_generator.rb +121 -0
  144. 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