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,95 @@
1
+ module ActiveGraph::Relationship::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 ActiveGraph::Relationship::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[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 ActiveGraph::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 ActiveGraph::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
+ ActiveGraph::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 [ActiveGraph::Node] node A node, persisted or unpersisted
81
+ # @param [Struct] res The result of calling `return` on a ActiveGraph::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.keys.include?(key)
87
+ unwrapped = res[key]
88
+ node.init_on_load(unwrapped, unwrapped.properties)
89
+ end
90
+
91
+ def node_symbols
92
+ self.class::NODE_SYMBOLS
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,92 @@
1
+ require 'active_graph/class_arguments'
2
+
3
+ module ActiveGraph::Relationship
4
+ module Property
5
+ extend ActiveSupport::Concern
6
+ include ActiveGraph::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}_id") { send(direction).neo_id if direction }
21
+ end
22
+
23
+ # @return [String] a string representing the relationship type that will be created
24
+ def type
25
+ self.class.type
26
+ end
27
+
28
+ def initialize(attributes = nil)
29
+ super(attributes)
30
+ end
31
+
32
+ def creates_unique_option
33
+ self.class.creates_unique_option
34
+ end
35
+
36
+ module ClassMethods
37
+ include ActiveGraph::Shared::Cypher::CreateMethod
38
+
39
+ # Extracts keys from attributes hash which are relationships of the model
40
+ # TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save?
41
+ def extract_association_attributes!(attributes)
42
+ return if attributes.blank?
43
+ {}.tap do |relationship_props|
44
+ attributes.each_key do |key|
45
+ relationship_props[key] = attributes.delete(key) if [:from_node, :to_node].include?(key)
46
+ end
47
+ end
48
+ end
49
+
50
+ def id_property_name
51
+ false
52
+ end
53
+
54
+ %w(to_class from_class).each do |direction|
55
+ define_method(direction.to_s) do |argument = nil|
56
+ if !argument.nil?
57
+ ActiveGraph::ClassArguments.validate_argument!(argument, direction)
58
+
59
+ instance_variable_set("@#{direction}", argument)
60
+ end
61
+
62
+ self.instance_variable_get("@#{direction}")
63
+ end
64
+
65
+ define_method("_#{direction}") { instance_variable_get "@#{direction}" }
66
+ end
67
+
68
+ def valid_class_argument?(class_argument)
69
+ [String, Symbol, FalseClass].include?(class_argument.class) ||
70
+ (class_argument.is_a?(Array) && class_argument.all? { |c| [String, Symbol].include?(c.class) })
71
+ end
72
+
73
+ alias start_class from_class
74
+ alias end_class to_class
75
+
76
+ def load_entity(id)
77
+ ActiveGraph::Node.load(id)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def load_nodes(from_node = nil, to_node = nil)
84
+ @from_node = RelatedNode.new(from_node)
85
+ @to_node = RelatedNode.new(to_node)
86
+ end
87
+
88
+ def inspect_attributes
89
+ attributes.to_a
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,99 @@
1
+ module ActiveGraph::Relationship
2
+ module Query
3
+ extend ActiveSupport::Concern
4
+
5
+ class RecordNotFound < ActiveGraph::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
+ def find(id)
11
+ fail "Unknown argument #{id.class} in find method (expected String or Integer)" if !(id.is_a?(String) || id.is_a?(Integer))
12
+ find_by_id(id)
13
+ end
14
+
15
+ # Loads the relationship using its neo_id.
16
+ def find_by_id(key)
17
+ query = ActiveGraph::Base.new_query
18
+ result = query.match('()-[r]-()').where('ID(r)' => key.to_i).limit(1).return(:r).first
19
+ fail RecordNotFound.new("Couldn't find #{name} with 'id'=#{key.inspect}", name, key) if result.blank?
20
+ result[:r]
21
+ end
22
+
23
+ # Performs a very basic match on the relationship.
24
+ # This is not executed lazily, it will immediately return matching objects.
25
+ # To use a string, prefix the property with "r1"
26
+ # @example Match with a string
27
+ # MyRelClass.where('r1.grade > r1')
28
+ def where(args = {})
29
+ where_query.where(where_string(args)).pluck(:r1)
30
+ end
31
+
32
+ # Performs a basic match on the relationship, returning all results.
33
+ # This is not executed lazily, it will immediately return matching objects.
34
+ def all
35
+ all_query.pluck(:r1)
36
+ end
37
+
38
+ def first
39
+ all_query.limit(1).order('ID(r1)').pluck(:r1).first
40
+ end
41
+
42
+ def last
43
+ all_query.limit(1).order('ID(r1) DESC').pluck(:r1).first
44
+ end
45
+
46
+ private
47
+
48
+ def deprecation_warning!
49
+ ActiveSupport::Deprecation.warn 'The ActiveGraph::Relationship::Query module has been deprecated and will be removed in a future version of the gem.', caller
50
+ end
51
+
52
+ def where_query
53
+ deprecation_warning!
54
+ ActiveGraph::Base.new_query.match("#{cypher_string(:outbound)}-[r1:`#{type}`]->#{cypher_string(:inbound)}")
55
+ end
56
+
57
+ def all_query
58
+ deprecation_warning!
59
+ ActiveGraph::Base.new_query.match("#{cypher_string}-[r1:`#{type}`]->#{cypher_string(:inbound)}")
60
+ end
61
+
62
+ def cypher_string(dir = :outbound)
63
+ case dir
64
+ when :outbound
65
+ identifier = '(n1'
66
+ identifier + (_from_class == :any ? ')' : cypher_label(:outbound))
67
+ when :inbound
68
+ identifier = '(n2'
69
+ identifier + (_to_class == :any ? ')' : cypher_label(:inbound))
70
+ end
71
+ end
72
+
73
+ def cypher_label(dir = :outbound)
74
+ target_class = dir == :outbound ? as_constant(_from_class) : as_constant(_to_class)
75
+ ":`#{target_class.mapped_label_name}`)"
76
+ end
77
+
78
+ def as_constant(given_class)
79
+ case given_class
80
+ when String, Symbol
81
+ given_class.to_s.constantize
82
+ when Array
83
+ fail "Relationship query methods are being deprecated and do not support Array (from|to)_class options. Current value: #{given_class}"
84
+ else
85
+ given_class
86
+ end
87
+ end
88
+
89
+ def where_string(args)
90
+ case args
91
+ when Hash
92
+ args.map { |k, v| v.is_a?(Integer) ? "r1.#{k} = #{v}" : "r1.#{k} = '#{v}'" }.join(', ')
93
+ else
94
+ args
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ wrapping_proc = proc do |relationship|
4
+ ActiveGraph::RelWrapping.wrapper(relationship)
5
+ end
6
+ Neo4j::Driver::Types::Relationship.wrapper_callback(wrapping_proc)
7
+
8
+ module ActiveGraph
9
+ module RelWrapping
10
+ class << self
11
+ def wrapper(rel)
12
+ rel.properties.symbolize_keys!
13
+ begin
14
+ most_concrete_class = class_from_type(rel.type).constantize
15
+ return rel unless most_concrete_class < ActiveGraph::Relationship
16
+ most_concrete_class.new
17
+ rescue NameError => e
18
+ raise e unless e.message =~ /(uninitialized|wrong) constant/
19
+
20
+ return rel
21
+ end.tap do |wrapped_rel|
22
+ wrapped_rel.init_on_load(rel, rel.start_node_id, rel.end_node_id, rel.type)
23
+ end
24
+ end
25
+
26
+ def class_from_type(type)
27
+ ActiveGraph::Relationship::Types::WRAPPED_CLASSES[type] || ActiveGraph::Relationship::Types::WRAPPED_CLASSES[type] = type.to_s.downcase.camelize
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,87 @@
1
+ module ActiveGraph::Relationship
2
+ # A container for Relationship's :inbound and :outbound methods. It provides lazy loading of nodes.
3
+ # It's important (or maybe not really IMPORTANT, but at least worth mentioning) that calling method_missing
4
+ # will result in a query to load the node if the node is not already loaded.
5
+ class RelatedNode
6
+ class UnsetRelatedNodeError < ActiveGraph::Error; end
7
+
8
+ # Relationship's related nodes can be initialized with nothing, an integer, or a fully wrapped node.
9
+ #
10
+ # Initialization with nothing happens when a new, non-persisted Relationship object is first initialized.
11
+ #
12
+ # Initialization with an integer happens when a relationship is loaded from the database. It loads using the ID
13
+ # because that is provided by the Cypher response and does not require an extra query.
14
+ def initialize(node = nil)
15
+ @node = valid_node_param?(node) ? node : (fail ActiveGraph::InvalidParameterError, 'RelatedNode must be initialized with either a node ID or node')
16
+ end
17
+
18
+ # Loads the node if needed, then conducts comparison.
19
+ def ==(other)
20
+ loaded if @node.is_a?(Integer)
21
+ @node == other
22
+ end
23
+
24
+ # Returns the neo_id of a given node without loading.
25
+ def neo_id
26
+ loaded? ? @node.neo_id : @node
27
+ end
28
+
29
+ # Loads a node from the database or returns the node if already laoded
30
+ def loaded
31
+ fail UnsetRelatedNodeError, 'Node not set, cannot load' if @node.nil?
32
+ @node = if @node.respond_to?(:neo_id)
33
+ @node
34
+ else
35
+ ActiveGraph::Base.new_query.match(:n).where(n: {neo_id: @node}).pluck(:n).first
36
+ end
37
+ end
38
+
39
+ # @param [String, Symbol, Array] clazz An alternate label to use in the event the node is not present or loaded
40
+ def cypher_representation(clazz)
41
+ case
42
+ when !set?
43
+ "(#{formatted_label_list(clazz)})"
44
+ when set? && !loaded?
45
+ "(Node with neo_id #{@node})"
46
+ else
47
+ node_class = self.class
48
+ id_name = node_class.id_property_name
49
+ labels = ':' + node_class.mapped_label_names.join(':')
50
+
51
+ "(#{labels} {#{id_name}: #{@node.id.inspect}})"
52
+ end
53
+ end
54
+
55
+ # @return [Boolean] indicates whether a node has or has not been fully loaded from the database
56
+ def loaded?
57
+ @node.respond_to?(:neo_id)
58
+ end
59
+
60
+ def set?
61
+ !@node.nil?
62
+ end
63
+
64
+ def method_missing(*args, &block)
65
+ loaded.send(*args, &block)
66
+ end
67
+
68
+ def respond_to_missing?(method_name, include_private = false)
69
+ loaded if @node.is_a?(Numeric)
70
+ @node.respond_to?(method_name) ? true : super
71
+ end
72
+
73
+ def class
74
+ loaded.send(:class)
75
+ end
76
+
77
+ private
78
+
79
+ def formatted_label_list(list)
80
+ list.is_a?(Array) ? list.join(' || ') : list
81
+ end
82
+
83
+ def valid_node_param?(node)
84
+ node.nil? || node.is_a?(Integer) || node.respond_to?(:neo_id)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,80 @@
1
+ module ActiveGraph
2
+ module Relationship
3
+ # provides mapping of type to model name
4
+ module Types
5
+ extend ActiveSupport::Concern
6
+
7
+ # WRAPPED_CLASSES maps relationship types to Relationship models.
8
+ #
9
+ # Typically, it's a 1:1 relationship, with a type having a model of the same name. Sometimes, someone needs to be a precious
10
+ # snowflake and have a model name that doesn't match the rel type, so this comes in handy.
11
+ #
12
+ # As an example, Chris often finds it easier to name models after the classes that use the relationship: `StudentLesson` instead of
13
+ # `EnrolledIn`, because it's easier to remember "A student has a relationship to lesson" than "the type of relationship between Student
14
+ # and Lesson is 'EnrolledIn'." After all, that is a big part of why we have models, right? To make our lives easier?
15
+ #
16
+ # A model is added to WRAPPED_CLASSES when it is initalized AND when the `type` class method is called within a model. This means that
17
+ # it's possible a model will be added twice: once with the type version of the model name, again with the custom type. deal_with_it.gif.
18
+ WRAPPED_CLASSES = {}
19
+
20
+ included do
21
+ type self.namespaced_model_name, true
22
+ end
23
+
24
+ module ClassMethods
25
+ include ActiveGraph::Shared::RelTypeConverters
26
+
27
+ def inherited(subclass)
28
+ subclass.type subclass.namespaced_model_name, true
29
+ end
30
+
31
+ # When called without arguments, it will return the current setting or supply a default.
32
+ # When called with arguments, it will change the current setting.
33
+ # @param [String] given_type sets the relationship type when creating relationships via this class
34
+ # @param [Boolean] auto Should the given_type be changed in compliance with the gem's rel decorator setting?
35
+ def type(given_type = nil, auto = false)
36
+ case
37
+ when !given_type && type?
38
+ @type
39
+ when given_type
40
+ assign_type!(given_type, auto)
41
+ else
42
+ assign_type!(namespaced_model_name, true)
43
+ end
44
+ end
45
+
46
+ def namespaced_model_name
47
+ case ActiveGraph::Config[:module_handling]
48
+ when :demodulize
49
+ self.name.demodulize
50
+ when Proc
51
+ ActiveGraph::Config[:module_handling].call(self.name)
52
+ else
53
+ self.name
54
+ end
55
+ end
56
+
57
+ def _wrapped_classes
58
+ WRAPPED_CLASSES
59
+ end
60
+
61
+ def add_wrapped_class(type)
62
+ # WRAPPED_CLASSES[type.to_sym.downcase] = self.name
63
+ _wrapped_classes[type.to_sym] = self.name
64
+ end
65
+
66
+ def type?
67
+ !!@type
68
+ end
69
+
70
+ private
71
+
72
+ def assign_type!(given_type, auto)
73
+ @type = (auto ? decorated_rel_type(given_type) : given_type).tap do |type|
74
+ add_wrapped_class(type)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end