neo4j_legacy 7.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1357 -0
- data/CONTRIBUTORS +8 -0
- data/Gemfile +38 -0
- data/README.md +103 -0
- data/bin/neo4j-jars +33 -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_support/per_thread_registry.rb +1 -0
- data/lib/backports/action_controller/metal/strong_parameters.rb +672 -0
- data/lib/backports/active_model/forbidden_attributes_protection.rb +30 -0
- data/lib/backports/active_support/concern.rb +13 -0
- data/lib/backports/active_support/core_ext/module/attribute_accessors.rb +10 -0
- data/lib/backports/active_support/logger.rb +99 -0
- data/lib/backports/active_support/logger_silence.rb +27 -0
- data/lib/backports/active_support/logger_thread_safe_level.rb +32 -0
- data/lib/backports/active_support/per_thread_registry.rb +60 -0
- data/lib/backports.rb +4 -0
- data/lib/neo4j/active_node/callbacks.rb +8 -0
- data/lib/neo4j/active_node/dependent/association_methods.rb +48 -0
- data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +50 -0
- data/lib/neo4j/active_node/dependent.rb +11 -0
- data/lib/neo4j/active_node/enum.rb +29 -0
- data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
- data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
- data/lib/neo4j/active_node/has_n/association.rb +280 -0
- data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
- data/lib/neo4j/active_node/has_n.rb +532 -0
- data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
- data/lib/neo4j/active_node/id_property.rb +187 -0
- data/lib/neo4j/active_node/initialize.rb +21 -0
- data/lib/neo4j/active_node/labels/index.rb +87 -0
- data/lib/neo4j/active_node/labels/reloading.rb +21 -0
- data/lib/neo4j/active_node/labels.rb +198 -0
- data/lib/neo4j/active_node/node_wrapper.rb +52 -0
- data/lib/neo4j/active_node/orm_adapter.rb +82 -0
- data/lib/neo4j/active_node/persistence.rb +175 -0
- data/lib/neo4j/active_node/property.rb +60 -0
- data/lib/neo4j/active_node/query/query_proxy.rb +361 -0
- data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +61 -0
- data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +90 -0
- data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
- data/lib/neo4j/active_node/query/query_proxy_link.rb +117 -0
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +210 -0
- data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +83 -0
- data/lib/neo4j/active_node/query.rb +76 -0
- data/lib/neo4j/active_node/query_methods.rb +65 -0
- data/lib/neo4j/active_node/reflection.rb +86 -0
- data/lib/neo4j/active_node/rels.rb +11 -0
- data/lib/neo4j/active_node/scope.rb +146 -0
- data/lib/neo4j/active_node/unpersisted.rb +48 -0
- data/lib/neo4j/active_node/validations.rb +59 -0
- data/lib/neo4j/active_node.rb +105 -0
- data/lib/neo4j/active_rel/callbacks.rb +15 -0
- data/lib/neo4j/active_rel/initialize.rb +28 -0
- data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
- data/lib/neo4j/active_rel/persistence.rb +114 -0
- data/lib/neo4j/active_rel/property.rb +95 -0
- data/lib/neo4j/active_rel/query.rb +95 -0
- data/lib/neo4j/active_rel/rel_wrapper.rb +22 -0
- data/lib/neo4j/active_rel/related_node.rb +83 -0
- data/lib/neo4j/active_rel/types.rb +82 -0
- data/lib/neo4j/active_rel/validations.rb +8 -0
- data/lib/neo4j/active_rel.rb +67 -0
- data/lib/neo4j/class_arguments.rb +39 -0
- data/lib/neo4j/config.rb +124 -0
- data/lib/neo4j/core/query.rb +22 -0
- data/lib/neo4j/errors.rb +28 -0
- data/lib/neo4j/migration.rb +127 -0
- data/lib/neo4j/paginated.rb +27 -0
- data/lib/neo4j/railtie.rb +169 -0
- data/lib/neo4j/schema/operation.rb +91 -0
- data/lib/neo4j/shared/attributes.rb +220 -0
- data/lib/neo4j/shared/callbacks.rb +64 -0
- data/lib/neo4j/shared/cypher.rb +37 -0
- data/lib/neo4j/shared/declared_properties.rb +204 -0
- data/lib/neo4j/shared/declared_property/index.rb +37 -0
- data/lib/neo4j/shared/declared_property.rb +118 -0
- data/lib/neo4j/shared/enum.rb +148 -0
- data/lib/neo4j/shared/filtered_hash.rb +79 -0
- data/lib/neo4j/shared/identity.rb +28 -0
- data/lib/neo4j/shared/initialize.rb +28 -0
- data/lib/neo4j/shared/marshal.rb +23 -0
- data/lib/neo4j/shared/mass_assignment.rb +58 -0
- data/lib/neo4j/shared/permitted_attributes.rb +28 -0
- data/lib/neo4j/shared/persistence.rb +231 -0
- data/lib/neo4j/shared/property.rb +220 -0
- data/lib/neo4j/shared/query_factory.rb +101 -0
- data/lib/neo4j/shared/rel_type_converters.rb +43 -0
- data/lib/neo4j/shared/serialized_properties.rb +30 -0
- data/lib/neo4j/shared/type_converters.rb +418 -0
- data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
- data/lib/neo4j/shared/typecaster.rb +53 -0
- data/lib/neo4j/shared/validations.rb +48 -0
- data/lib/neo4j/shared.rb +51 -0
- data/lib/neo4j/tasks/migration.rake +24 -0
- data/lib/neo4j/timestamps/created.rb +9 -0
- data/lib/neo4j/timestamps/updated.rb +9 -0
- data/lib/neo4j/timestamps.rb +11 -0
- data/lib/neo4j/type_converters.rb +7 -0
- data/lib/neo4j/version.rb +3 -0
- data/lib/neo4j/wrapper.rb +4 -0
- data/lib/neo4j.rb +96 -0
- data/lib/rails/generators/neo4j/model/model_generator.rb +86 -0
- data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
- data/lib/rails/generators/neo4j_generator.rb +67 -0
- data/neo4j.gemspec +43 -0
- metadata +389 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
module Neo4j::ActiveNode
|
2
|
+
# This module makes it possible to use other IDs than the build it neo4j id (neo_id)
|
3
|
+
#
|
4
|
+
# @example using generated UUIDs
|
5
|
+
# class Person
|
6
|
+
# include Neo4j::ActiveNode
|
7
|
+
# # creates a 'secret' neo4j property my_uuid which will be used as primary key
|
8
|
+
# id_property :my_uuid, auto: :uuid
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# @example using user defined ids
|
12
|
+
# class Person
|
13
|
+
# include Neo4j::ActiveNode
|
14
|
+
# property :title
|
15
|
+
# validates :title, :presence => true
|
16
|
+
# id_property :title_id, on: :title_to_url
|
17
|
+
#
|
18
|
+
# def title_to_url
|
19
|
+
# self.title.urlize # uses https://github.com/cheef/string-urlize gem
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example using already exsting ids that you don't want a constraint added to
|
24
|
+
# class Person
|
25
|
+
# include Neo4j::ActiveNode
|
26
|
+
# property :title
|
27
|
+
# validates :title, :presence => true
|
28
|
+
# id_property :id, on: :id_builder, constraint: false
|
29
|
+
#
|
30
|
+
# def id_builder
|
31
|
+
# # only need to fill this out if you're gonna write to the db
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
module IdProperty
|
36
|
+
extend ActiveSupport::Concern
|
37
|
+
include Accessor
|
38
|
+
|
39
|
+
module TypeMethods
|
40
|
+
def define_id_methods(clazz, name, conf)
|
41
|
+
validate_conf!(conf)
|
42
|
+
|
43
|
+
if conf[:on]
|
44
|
+
define_custom_method(clazz, name, conf[:on])
|
45
|
+
elsif conf[:auto]
|
46
|
+
define_uuid_method(clazz, name)
|
47
|
+
elsif conf.empty?
|
48
|
+
define_property_method(clazz, name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def validate_conf!(conf)
|
55
|
+
fail "Expected a Hash, got #{conf.class} (#{conf}) for id_property" if !conf.is_a?(Hash)
|
56
|
+
|
57
|
+
return if conf[:on]
|
58
|
+
|
59
|
+
if conf[:auto]
|
60
|
+
fail "only :uuid auto id_property allowed, got #{conf[:auto]}" if conf[:auto] != :uuid
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
return if conf.empty?
|
65
|
+
|
66
|
+
fail "Illegal value #{conf.inspect} for id_property, expected :on or :auto"
|
67
|
+
end
|
68
|
+
|
69
|
+
def define_property_method(clazz, name)
|
70
|
+
clear_methods(clazz, name)
|
71
|
+
|
72
|
+
clazz.module_eval(%(
|
73
|
+
def id
|
74
|
+
_persisted_obj ? #{name.to_sym == :id ? 'attribute(\'id\')' : name} : nil
|
75
|
+
end
|
76
|
+
|
77
|
+
property :#{name}
|
78
|
+
), __FILE__, __LINE__)
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def define_uuid_method(clazz, name)
|
83
|
+
clear_methods(clazz, name)
|
84
|
+
|
85
|
+
clazz.module_eval(%(
|
86
|
+
default_property :#{name} do
|
87
|
+
::SecureRandom.uuid
|
88
|
+
end
|
89
|
+
|
90
|
+
def #{name}
|
91
|
+
default_property_value
|
92
|
+
end
|
93
|
+
|
94
|
+
alias_method :id, :#{name}
|
95
|
+
), __FILE__, __LINE__)
|
96
|
+
end
|
97
|
+
|
98
|
+
def define_custom_method(clazz, name, on)
|
99
|
+
clear_methods(clazz, name)
|
100
|
+
|
101
|
+
clazz.module_eval(%{
|
102
|
+
default_property :#{name} do |instance|
|
103
|
+
raise "Specifying custom id_property #{name} on non-existent method #{on}" unless instance.respond_to?(:#{on})
|
104
|
+
instance.#{on}
|
105
|
+
end
|
106
|
+
|
107
|
+
def #{name}
|
108
|
+
default_property_value
|
109
|
+
end
|
110
|
+
|
111
|
+
alias_method :id, :#{name}
|
112
|
+
}, __FILE__, __LINE__)
|
113
|
+
end
|
114
|
+
|
115
|
+
def clear_methods(clazz, name)
|
116
|
+
clazz.module_eval(%(undef_method :#{name}), __FILE__, __LINE__) if clazz.method_defined?(name)
|
117
|
+
clazz.module_eval(%(undef_property :#{name}), __FILE__, __LINE__) if clazz.attribute_names.include?(name.to_s)
|
118
|
+
end
|
119
|
+
|
120
|
+
extend self
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
module ClassMethods
|
125
|
+
attr_accessor :manual_id_property
|
126
|
+
|
127
|
+
def find_by_neo_id(id)
|
128
|
+
Neo4j::Node.load(id)
|
129
|
+
end
|
130
|
+
|
131
|
+
def find_by_id(id)
|
132
|
+
all.where(id_property_name => id).first
|
133
|
+
end
|
134
|
+
|
135
|
+
def find_by_ids(ids)
|
136
|
+
all.where(id_property_name => ids).to_a
|
137
|
+
end
|
138
|
+
|
139
|
+
def id_property(name, conf = {})
|
140
|
+
self.manual_id_property = true
|
141
|
+
Neo4j::Session.on_next_session_available do |_|
|
142
|
+
@id_property_info = {name: name, type: conf}
|
143
|
+
TypeMethods.define_id_methods(self, name, conf)
|
144
|
+
constraint(name, type: :unique) unless conf[:constraint] == false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# rubocop:disable Style/PredicateName
|
149
|
+
def has_id_property?
|
150
|
+
ActiveSupport::Deprecation.warn 'has_id_property? is deprecated and may be removed from future releases, use id_property? instead.', caller
|
151
|
+
|
152
|
+
id_property?
|
153
|
+
end
|
154
|
+
# rubocop:enable Style/PredicateName
|
155
|
+
|
156
|
+
def id_property?
|
157
|
+
id_property_info && !id_property_info.empty?
|
158
|
+
end
|
159
|
+
|
160
|
+
def id_property_info
|
161
|
+
@id_property_info ||= {}
|
162
|
+
end
|
163
|
+
|
164
|
+
def id_property_name
|
165
|
+
id_property_info[:name]
|
166
|
+
end
|
167
|
+
|
168
|
+
def manual_id_property?
|
169
|
+
!!manual_id_property
|
170
|
+
end
|
171
|
+
|
172
|
+
alias_method :primary_key, :id_property_name
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def id_property_constraint(name)
|
177
|
+
if id_property?
|
178
|
+
unless mapped_label.uniqueness_constraints[:property_keys].include?([name])
|
179
|
+
# Neo4j Embedded throws a crazy error when a constraint can't be dropped
|
180
|
+
drop_constraint(id_property_name, type: :unique) if constraint?(mapped_label_name, id_property_name)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
rescue Neo4j::Server::CypherResponse::ResponseError, Java::OrgNeo4jCypher::CypherExecutionException
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Neo4j::ActiveNode::Initialize
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
include Neo4j::Shared::Initialize
|
4
|
+
|
5
|
+
attr_reader :called_by
|
6
|
+
|
7
|
+
# called when loading the node from the database
|
8
|
+
# @param [Neo4j::Node] persisted_node the node this class wraps
|
9
|
+
# @param [Hash] properties of the persisted node.
|
10
|
+
def init_on_load(persisted_node, properties)
|
11
|
+
self.class.extract_association_attributes!(properties)
|
12
|
+
@_persisted_obj = persisted_node
|
13
|
+
changed_attributes && changed_attributes.clear
|
14
|
+
@attributes = convert_and_assign_attributes(properties)
|
15
|
+
end
|
16
|
+
|
17
|
+
def init_on_reload(reloaded)
|
18
|
+
@attributes = nil
|
19
|
+
init_on_load(reloaded, reloaded.props)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Neo4j::ActiveNode::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 Neo4j::ActiveNode::Property::ClassMethods#property.
|
13
|
+
#
|
14
|
+
# @param [Symbol] property the property we want a Neo4j index on
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class Person
|
18
|
+
# include Neo4j::ActiveNode
|
19
|
+
# property :name
|
20
|
+
# index :name
|
21
|
+
# end
|
22
|
+
def index(property)
|
23
|
+
Neo4j::Session.on_next_session_available do |_|
|
24
|
+
declared_properties.index_or_fail!(property, id_property_name)
|
25
|
+
schema_create_operation(:index, property)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a neo4j constraint on this class for given property
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# Person.constraint :name, type: :unique
|
33
|
+
def constraint(property, constraints = {type: :unique})
|
34
|
+
Neo4j::Session.on_next_session_available do
|
35
|
+
declared_properties.constraint_or_fail!(property, id_property_name)
|
36
|
+
schema_create_operation(:constraint, property, constraints)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [Symbol] property The name of the property index to be dropped
|
41
|
+
def drop_index(property, options = {})
|
42
|
+
Neo4j::Session.on_next_session_available do
|
43
|
+
declared_properties[property].unindex! if declared_properties[property]
|
44
|
+
schema_drop_operation(:index, property, options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param [Symbol] property The name of the property constraint to be dropped
|
49
|
+
# @param [Hash] constraint The constraint type to be dropped.
|
50
|
+
def drop_constraint(property, constraint = {type: :unique})
|
51
|
+
Neo4j::Session.on_next_session_available do
|
52
|
+
declared_properties[property].unconstraint! if declared_properties[property]
|
53
|
+
schema_drop_operation(:constraint, property, constraint)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def index?(property)
|
58
|
+
mapped_label.indexes[:property_keys].include?([property])
|
59
|
+
end
|
60
|
+
|
61
|
+
def constraint?(property)
|
62
|
+
mapped_label.unique_constraints[:property_keys].include?([property])
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def schema_create_operation(type, property, options = {})
|
68
|
+
new_schema_class(type, property, options).create!
|
69
|
+
end
|
70
|
+
|
71
|
+
def schema_drop_operation(type, property, options = {})
|
72
|
+
new_schema_class(type, property, options).drop!
|
73
|
+
end
|
74
|
+
|
75
|
+
def new_schema_class(type, property, options)
|
76
|
+
case type
|
77
|
+
when :index
|
78
|
+
Neo4j::Schema::ExactIndexOperation
|
79
|
+
when :constraint
|
80
|
+
Neo4j::Schema::UniqueConstraintOperation
|
81
|
+
else
|
82
|
+
fail "Unknown Schema Operation class #{type}"
|
83
|
+
end.new(mapped_label_name, property, options)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Neo4j::ActiveNode::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,198 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module ActiveNode
|
3
|
+
# Provides a mapping between neo4j labels and Ruby classes
|
4
|
+
module Labels
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include Neo4j::ActiveNode::Labels::Index
|
7
|
+
include Neo4j::ActiveNode::Labels::Reloading
|
8
|
+
|
9
|
+
WRAPPED_CLASSES = []
|
10
|
+
MODELS_FOR_LABELS_CACHE = {}
|
11
|
+
MODELS_FOR_LABELS_CACHE.clear
|
12
|
+
|
13
|
+
included do |model|
|
14
|
+
Neo4j::ActiveNode::Labels.clear_wrapped_models
|
15
|
+
|
16
|
+
Neo4j::ActiveNode::Labels.add_wrapped_class(model) unless Neo4j::ActiveNode::Labels._wrapped_classes.include?(model)
|
17
|
+
end
|
18
|
+
|
19
|
+
class RecordNotFound < Neo4j::RecordNotFound; end
|
20
|
+
|
21
|
+
# @return the labels
|
22
|
+
# @see Neo4j-core
|
23
|
+
def labels
|
24
|
+
@_persisted_obj.labels
|
25
|
+
end
|
26
|
+
|
27
|
+
# this is handled by core, leaving it now for posterity
|
28
|
+
# def queried_labels
|
29
|
+
# self.class.query_as(:result).where("ID(result)" => self.neo_id).return("LABELS(result) as result_labels").first.result_labels.map(&:to_sym)
|
30
|
+
# end
|
31
|
+
|
32
|
+
# adds one or more labels
|
33
|
+
# @see Neo4j-core
|
34
|
+
def add_label(*label)
|
35
|
+
@_persisted_obj.add_label(*label)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Removes one or more labels
|
39
|
+
# Be careful, don't remove the label representing the Ruby class.
|
40
|
+
# @see Neo4j-core
|
41
|
+
def remove_label(*label)
|
42
|
+
@_persisted_obj.remove_label(*label)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self._wrapped_classes
|
46
|
+
WRAPPED_CLASSES
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.add_wrapped_class(model)
|
50
|
+
_wrapped_classes << model
|
51
|
+
end
|
52
|
+
|
53
|
+
# Finds an appropriate matching model given a set of labels
|
54
|
+
# which are assigned to a node
|
55
|
+
def self.model_for_labels(labels)
|
56
|
+
return MODELS_FOR_LABELS_CACHE[labels] if MODELS_FOR_LABELS_CACHE[labels]
|
57
|
+
|
58
|
+
models = WRAPPED_CLASSES.select do |model|
|
59
|
+
(model.mapped_label_names - labels).size == 0
|
60
|
+
end
|
61
|
+
|
62
|
+
MODELS_FOR_LABELS_CACHE[labels] = models.max_by do |model|
|
63
|
+
(model.mapped_label_names & labels).size
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.clear_wrapped_models
|
68
|
+
MODELS_FOR_LABELS_CACHE.clear
|
69
|
+
Neo4j::Node::Wrapper::CONSTANTS_FOR_LABELS_CACHE.clear
|
70
|
+
end
|
71
|
+
|
72
|
+
module ClassMethods
|
73
|
+
include Neo4j::ActiveNode::QueryMethods
|
74
|
+
|
75
|
+
delegate :update_all, to: :all
|
76
|
+
|
77
|
+
# Returns the object with the specified neo4j id.
|
78
|
+
# @param [String,Integer] id of node to find
|
79
|
+
def find(id)
|
80
|
+
map_id = proc { |object| object.respond_to?(:id) ? object.send(:id) : object }
|
81
|
+
|
82
|
+
result = find_by_id_or_ids(map_id, id)
|
83
|
+
|
84
|
+
fail RecordNotFound.new(
|
85
|
+
"Couldn't find #{name} with '#{id_property_name}'=#{id}",
|
86
|
+
name, id_property_name, id) if result.blank?
|
87
|
+
result.tap { |r| find_callbacks!(r) }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
|
91
|
+
# @param values Hash args of arguments to find
|
92
|
+
def find_by(values)
|
93
|
+
all.where(values).limit(1).query_as(:n).pluck(:n).first
|
94
|
+
end
|
95
|
+
|
96
|
+
# Like find_by, except that if no record is found, raises a RecordNotFound error.
|
97
|
+
def find_by!(values)
|
98
|
+
find_by(values) || fail(RecordNotFound, "#{self.query_as(:n).where(n: values).limit(1).to_cypher} returned no results")
|
99
|
+
end
|
100
|
+
|
101
|
+
# Deletes all nodes and connected relationships from Cypher.
|
102
|
+
def delete_all
|
103
|
+
self.neo4j_session._query("MATCH (n:`#{mapped_label_name}`) OPTIONAL MATCH (n)-[r]-() DELETE n,r")
|
104
|
+
self.neo4j_session._query("MATCH (n:`#{mapped_label_name}`) DELETE n")
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns each node to Ruby and calls `destroy`. Be careful, as this can be a very slow operation if you have many nodes. It will generate at least
|
108
|
+
# one database query per node in the database, more if callbacks require them.
|
109
|
+
def destroy_all
|
110
|
+
all.each(&:destroy)
|
111
|
+
end
|
112
|
+
|
113
|
+
# @return [Array{Symbol}] all the labels that this class has
|
114
|
+
def mapped_label_names
|
115
|
+
self.ancestors.find_all { |a| a.respond_to?(:mapped_label_name) }.map { |a| a.mapped_label_name.to_sym }
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [Symbol] the label that this class has which corresponds to a Ruby class
|
119
|
+
def mapped_label_name
|
120
|
+
@mapped_label_name || label_for_model
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Neo4j::Label] the label for this class
|
124
|
+
def mapped_label
|
125
|
+
Neo4j::Label.create(mapped_label_name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def base_class
|
129
|
+
unless self < Neo4j::ActiveNode
|
130
|
+
fail "#{name} doesn't belong in a hierarchy descending from ActiveNode"
|
131
|
+
end
|
132
|
+
|
133
|
+
if superclass == Object
|
134
|
+
self
|
135
|
+
else
|
136
|
+
superclass.base_class
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
protected
|
141
|
+
|
142
|
+
def mapped_labels
|
143
|
+
mapped_label_names.map { |label_name| Neo4j::Label.create(label_name) }
|
144
|
+
end
|
145
|
+
|
146
|
+
def mapped_label_name=(name)
|
147
|
+
@mapped_label_name = name.to_sym
|
148
|
+
end
|
149
|
+
|
150
|
+
# rubocop:disable Style/AccessorMethodName
|
151
|
+
def set_mapped_label_name(name)
|
152
|
+
ActiveSupport::Deprecation.warn 'set_mapped_label_name is deprecated, use self.mapped_label_name= instead.', caller
|
153
|
+
|
154
|
+
self.mapped_label_name = name
|
155
|
+
end
|
156
|
+
# rubocop:enable Style/AccessorMethodName
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def find_by_id_or_ids(map_id, id)
|
161
|
+
if id.is_a?(Array)
|
162
|
+
find_by_ids(id.map(&map_id))
|
163
|
+
else
|
164
|
+
find_by_id(map_id.call(id))
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def find_callbacks!(result)
|
169
|
+
case result
|
170
|
+
when Neo4j::ActiveNode
|
171
|
+
result.run_callbacks(:find)
|
172
|
+
when Array
|
173
|
+
result.each { |r| find_callbacks!(r) }
|
174
|
+
else
|
175
|
+
result
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def label_for_model
|
180
|
+
(self.name.nil? ? object_id.to_s.to_sym : decorated_label_name)
|
181
|
+
end
|
182
|
+
|
183
|
+
def decorated_label_name
|
184
|
+
name = case Neo4j::Config[:module_handling]
|
185
|
+
when :demodulize
|
186
|
+
self.name.demodulize
|
187
|
+
when Proc
|
188
|
+
Neo4j::Config[:module_handling].call self.name
|
189
|
+
else
|
190
|
+
self.name
|
191
|
+
end
|
192
|
+
|
193
|
+
name.to_sym
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
class Neo4j::Node
|
4
|
+
# The wrapping process is what transforms a raw CypherNode or EmbeddedNode from Neo4j::Core into a healthy ActiveNode (or ActiveRel) object.
|
5
|
+
module Wrapper
|
6
|
+
# this is a plugin in the neo4j-core so that the Ruby wrapper will be wrapped around the Neo4j::Node objects
|
7
|
+
def wrapper
|
8
|
+
found_class = class_to_wrap
|
9
|
+
return self if not found_class
|
10
|
+
|
11
|
+
found_class.new.tap do |wrapped_node|
|
12
|
+
wrapped_node.init_on_load(self, self.props)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def class_to_wrap
|
17
|
+
load_classes_from_labels
|
18
|
+
Neo4j::ActiveNode::Labels.model_for_labels(labels).tap do |model_class|
|
19
|
+
Neo4j::Node::Wrapper.populate_constants_for_labels_cache(model_class, labels)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def load_classes_from_labels
|
26
|
+
labels.each { |label| Neo4j::Node::Wrapper.constant_for_label(label) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Only load classes once for performance
|
30
|
+
CONSTANTS_FOR_LABELS_CACHE = {}
|
31
|
+
|
32
|
+
def self.constant_for_label(label)
|
33
|
+
CONSTANTS_FOR_LABELS_CACHE[label] || CONSTANTS_FOR_LABELS_CACHE[label] = constantized_label(label)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.constantized_label(label)
|
37
|
+
"#{association_model_namespace}::#{label}".constantize
|
38
|
+
rescue NameError
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.populate_constants_for_labels_cache(model_class, labels)
|
43
|
+
labels.each do |label|
|
44
|
+
CONSTANTS_FOR_LABELS_CACHE[label] = model_class if CONSTANTS_FOR_LABELS_CACHE[label].nil?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.association_model_namespace
|
49
|
+
Neo4j::Config.association_model_namespace_string
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'orm_adapter'
|
2
|
+
|
3
|
+
module Neo4j
|
4
|
+
module ActiveNode
|
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
|