/
labels.rb
259 lines (213 loc) · 8.09 KB
/
labels.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
module Neo4j
module ActiveNode
# Provides a mapping between neo4j labels and Ruby classes
module Labels
extend ActiveSupport::Concern
WRAPPED_CLASSES = []
MODELS_FOR_LABELS_CACHE = {}
MODELS_FOR_LABELS_CACHE.clear
included do |model|
def self.inherited(model)
add_wrapped_class(model)
super
end
Neo4j::ActiveNode::Labels.add_wrapped_class(model) unless Neo4j::ActiveNode::Labels._wrapped_classes.include?(model)
end
class InvalidQueryError < StandardError; end
class RecordNotFound < StandardError; end
# @return the labels
# @see Neo4j-core
def labels
@_persisted_obj.labels
end
# this is handled by core, leaving it now for posterity
# def queried_labels
# self.class.query_as(:result).where("ID(result)" => self.neo_id).return("LABELS(result) as result_labels").first.result_labels.map(&:to_sym)
# end
# adds one or more labels
# @see Neo4j-core
def add_label(*label)
@_persisted_obj.add_label(*label)
end
# Removes one or more labels
# Be careful, don't remove the label representing the Ruby class.
# @see Neo4j-core
def remove_label(*label)
@_persisted_obj.remove_label(*label)
end
def self.add_wrapped_class(model)
_wrapped_classes << model
end
def self._wrapped_classes
Neo4j::ActiveNode::Labels::WRAPPED_CLASSES
end
def self.model_for_labels(labels)
MODELS_FOR_LABELS_CACHE[labels] || model_cache(labels)
end
def self.model_cache(labels)
models = WRAPPED_CLASSES.select do |model|
(model.mapped_label_names - labels).size == 0
end
MODELS_FOR_LABELS_CACHE[labels] = models.max do |model|
(model.mapped_label_names & labels).size
end
end
def self.clear_model_for_label_cache
MODELS_FOR_LABELS_CACHE.clear
end
def self.clear_wrapped_models
WRAPPED_CLASSES.clear
end
protected
module ClassMethods
include Neo4j::ActiveNode::QueryMethods
# Returns the object with the specified neo4j id.
# @param [String,Integer] id of node to find
def find(id)
map_id = proc { |object| object.respond_to?(:id) ? object.send(:id) : object }
result = if id.is_a?(Array)
find_by_ids(id.map { |o| map_id.call(o) })
else
find_by_id(map_id.call(id))
end
fail Neo4j::RecordNotFound if result.blank?
result
end
# Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
# @param Hash args of arguments to find
def find_by(values)
all.query_as(:n).where(n: values).limit(1).pluck(:n).first
end
# Like find_by, except that if no record is found, raises a RecordNotFound error.
def find_by!(values)
find_by(values) || fail(RecordNotFound, "#{self.query_as(:n).where(n: values).limit(1).to_cypher} returned no results")
end
# Deletes all nodes and connected relationships from Cypher.
def delete_all
self.neo4j_session._query("MATCH (n:`#{mapped_label_name}`) OPTIONAL MATCH n-[r]-() DELETE n,r")
self.neo4j_session._query("MATCH (n:`#{mapped_label_name}`) DELETE n")
end
# 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
# one database query per node in the database, more if callbacks require them.
def destroy_all
all.each(&:destroy)
end
# Creates a Neo4j index on given property
#
# This can also be done on the property directly, see Neo4j::ActiveNode::Property::ClassMethods#property.
#
# @param [Symbol] property the property we want a Neo4j index on
# @param [Hash] conf optional property configuration
#
# @example
# class Person
# include Neo4j::ActiveNode
# property :name
# index :name
# end
#
# @example with constraint
# class Person
# include Neo4j::ActiveNode
# property :name
#
# # below is same as: index :name, index: :exact, constraint: {type: :unique}
# index :name, constraint: {type: :unique}
# end
def index(property, conf = {})
Neo4j::Session.on_session_available do |_|
_index(property, conf)
end
indexed_properties.push property unless indexed_properties.include? property
end
# Creates a neo4j constraint on this class for given property
#
# @example
# Person.constraint :name, type: :unique
#
def constraint(property, constraints)
Neo4j::Session.on_session_available do |session|
unless Neo4j::Label.constraint?(mapped_label_name, property)
label = Neo4j::Label.create(mapped_label_name)
label.create_constraint(property, constraints, session)
end
end
end
def drop_constraint(property, constraint)
Neo4j::Session.on_session_available do |session|
label = Neo4j::Label.create(mapped_label_name)
label.drop_constraint(property, constraint, session)
end
end
def index?(index_def)
mapped_label.indexes[:property_keys].include?([index_def])
end
# @return [Array{Symbol}] all the labels that this class has
def mapped_label_names
self.ancestors.find_all { |a| a.respond_to?(:mapped_label_name) }.map { |a| a.mapped_label_name.to_sym }
end
# @return [Symbol] the label that this class has which corresponds to a Ruby class
def mapped_label_name
@mapped_label_name || label_for_model
end
# @return [Neo4j::Label] the label for this class
def mapped_label
Neo4j::Label.create(mapped_label_name)
end
def indexed_properties
@_indexed_properties ||= []
end
def base_class
unless self < Neo4j::ActiveNode
fail "#{name} doesn't belong in a hierarchy descending from ActiveNode"
end
if superclass == Object
self
else
superclass.base_class
end
end
protected
def _index(property, conf)
mapped_labels.each do |label|
# make sure the property is not indexed twice
existing = label.indexes[:property_keys]
# In neo4j constraint automatically creates an index
if conf[:constraint]
constraint(property, conf[:constraint])
else
label.create_index(property) unless existing.flatten.include?(property)
end
end
end
def mapped_labels
mapped_label_names.map { |label_name| Neo4j::Label.create(label_name) }
end
def mapped_label_name=(name)
@mapped_label_name = name.to_sym
end
# rubocop:disable Style/AccessorMethodName
def set_mapped_label_name(name)
ActiveSupport::Deprecation.warn 'set_mapped_label_name is deprecated, use self.mapped_label_name= instead.', caller
self.mapped_label_name = name
end
# rubocop:enable Style/AccessorMethodName
private
def label_for_model
(self.name.nil? ? object_id.to_s.to_sym : decorated_label_name)
end
def decorated_label_name
name = case Neo4j::Config[:module_handling]
when :demodulize
self.name.demodulize
when Proc
Neo4j::Config[:module_handling].call self.name
else
self.name
end
name.to_sym
end
end
end
end
end