activecypher 0.12.2 → 0.13.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29fa2252b85c3c67d4f8a8e72acc69ad1021088561ce02c8e1d205bef28b4069
4
- data.tar.gz: 0665ef0b5e7a68264f50b846ba78036dc5d3c805e874bc475abb12a3bb699069
3
+ metadata.gz: 96c24fcfa519e4e44a2e8c8d1e84ae0b975d0d73d25492b025f4968e148a2090
4
+ data.tar.gz: f9ef843ec7859048e7284731122be13ec519f9d44066471db70db1d4d326198f
5
5
  SHA512:
6
- metadata.gz: 13b575ead30e4ea3b3476e97993c33e8eebc2dfb788d10e7ad9aa8e02014f389581d3e619f50629611340ea33af2bae967b026cd07110de57586887919ff89f3
7
- data.tar.gz: a9dd762af2674169b558739ca5652e21cfcb60adb8a491c5d0bdf22b64a19b45c1087858db972a71f26af9113ee146e67ef57188e6f273adef959ca369a407d3
6
+ metadata.gz: 0afa400aeab7bfc05a7842cf4361f064b052ec52c8d2fc402da767da882277e8ad1b2c14a2b3c6396c3d34cc436caa45cb43978727d4cd02da80e589af197b10
7
+ data.tar.gz: e76af4465bdfb80f6b388137e849b2b2705e1f9fd8ae5afd81369f19d40c144819cc5eba6f106677a37932b7a0bbab864951fef2e5fb2450aca982e81971065b
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'extension'
4
+
5
+ module ActiveCypher
6
+ module RailsLensExt
7
+ # Annotator for ActiveCypher graph models
8
+ # Discovers and annotates Node and Relationship classes
9
+ # Uses RailsLens-compatible TOML format and markers
10
+ class Annotator
11
+ # Use RailsLens-compatible marker format
12
+ MARKER_FORMAT = 'rails-lens:graph'
13
+ ANNOTATION_BEGIN = "# <#{MARKER_FORMAT}:begin>".freeze
14
+ ANNOTATION_END = "# <#{MARKER_FORMAT}:end>".freeze
15
+
16
+ class << self
17
+ # Annotate all ActiveCypher models
18
+ # @param options [Hash] Options for annotation
19
+ # @option options [Boolean] :include_abstract Include abstract classes
20
+ # @option options [Array<String>] :only Only annotate these models
21
+ # @option options [Array<String>] :except Skip these models
22
+ # @return [Hash] Results with :annotated, :skipped, :failed keys
23
+ def annotate_all(options = {})
24
+ results = { annotated: [], skipped: [], failed: [] }
25
+
26
+ models = discover_models(options)
27
+
28
+ models.each do |model|
29
+ result = annotate_model(model, options)
30
+ case result[:status]
31
+ when :annotated
32
+ results[:annotated] << result
33
+ when :skipped
34
+ results[:skipped] << result
35
+ when :failed
36
+ results[:failed] << result
37
+ end
38
+ end
39
+
40
+ results
41
+ end
42
+
43
+ # Remove annotations from all ActiveCypher models
44
+ # @param options [Hash] Options for removal
45
+ # @return [Hash] Results with :removed, :skipped keys
46
+ def remove_all(options = {})
47
+ results = { removed: [], skipped: [] }
48
+
49
+ models = discover_models(options.merge(include_abstract: true))
50
+
51
+ models.each do |model|
52
+ result = remove_annotation(model)
53
+ if result[:status] == :removed
54
+ results[:removed] << result
55
+ else
56
+ results[:skipped] << result
57
+ end
58
+ end
59
+
60
+ results
61
+ end
62
+
63
+ # Annotate a single model
64
+ # @param model [Class] The model class to annotate
65
+ # @param options [Hash] Options
66
+ # @return [Hash] Result with :status, :model, :file, :message keys
67
+ def annotate_model(model, _options = {})
68
+ file_path = model_file_path(model)
69
+
70
+ return { status: :skipped, model: model.name, file: nil, message: 'File not found' } unless file_path && File.exist?(file_path)
71
+
72
+ extension = Extension.new(model)
73
+ annotation = extension.annotate
74
+
75
+ return { status: :skipped, model: model.name, file: file_path, message: 'No annotation generated' } unless annotation
76
+
77
+ begin
78
+ write_annotation(file_path, model, annotation)
79
+ { status: :annotated, model: model.name, file: file_path, message: 'Annotated successfully' }
80
+ rescue StandardError => e
81
+ { status: :failed, model: model.name, file: file_path, message: e.message }
82
+ end
83
+ end
84
+
85
+ # Remove annotation from a single model
86
+ # @param model [Class] The model class
87
+ # @return [Hash] Result with :status, :model, :file keys
88
+ def remove_annotation(model)
89
+ file_path = model_file_path(model)
90
+
91
+ return { status: :skipped, model: model.name, file: nil } unless file_path && File.exist?(file_path)
92
+
93
+ content = File.read(file_path)
94
+
95
+ if content.include?(ANNOTATION_BEGIN)
96
+ new_content = ::RailsLens::FileInsertionHelper.remove_after_frozen_string_literal(
97
+ content, '<rails-lens:graph:begin>', '<rails-lens:graph:end>'
98
+ )
99
+ new_content = new_content.gsub(/\n{3,}/, "\n\n")
100
+
101
+ File.write(file_path, new_content)
102
+ { status: :removed, model: model.name, file: file_path }
103
+ else
104
+ { status: :skipped, model: model.name, file: file_path }
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ # Discover all ActiveCypher models
111
+ def discover_models(options = {})
112
+ # Eager load all graph models
113
+ eager_load_graph_models
114
+
115
+ models = []
116
+
117
+ # Find all Node classes (ActiveCypher::Base descendants)
118
+ if defined?(::ActiveCypher::Base)
119
+ ObjectSpace.each_object(Class) do |klass|
120
+ next unless klass < ::ActiveCypher::Base
121
+ next if klass == ::ActiveCypher::Base
122
+
123
+ models << klass
124
+ end
125
+ end
126
+
127
+ # Find all Relationship classes (ActiveCypher::Relationship descendants)
128
+ if defined?(::ActiveCypher::Relationship)
129
+ ObjectSpace.each_object(Class) do |klass|
130
+ next unless klass < ::ActiveCypher::Relationship
131
+ next if klass == ::ActiveCypher::Relationship
132
+
133
+ models << klass
134
+ end
135
+ end
136
+
137
+ # Filter out abstract classes unless requested
138
+ models.reject! { |m| m.respond_to?(:abstract_class?) && m.abstract_class? } unless options[:include_abstract]
139
+
140
+ # Filter by :only option
141
+ if options[:only]
142
+ only_names = Array(options[:only]).map(&:to_s)
143
+ models.select! { |m| only_names.include?(m.name) }
144
+ end
145
+
146
+ # Filter by :except option
147
+ if options[:except]
148
+ except_names = Array(options[:except]).map(&:to_s)
149
+ models.reject! { |m| except_names.include?(m.name) }
150
+ end
151
+
152
+ models.sort_by { |m| m.name || '' }
153
+ end
154
+
155
+ # Eager load graph models from Rails app
156
+ def eager_load_graph_models
157
+ return unless defined?(Rails) && Rails.respond_to?(:root)
158
+
159
+ # Common paths for graph models
160
+ graph_paths = [
161
+ Rails.root.join('app', 'graph'),
162
+ Rails.root.join('app', 'models', 'graph'),
163
+ Rails.root.join('app', 'graphs')
164
+ ]
165
+
166
+ graph_paths.each do |path|
167
+ next unless path.exist?
168
+
169
+ Dir.glob(path.join('**', '*.rb')).each do |file|
170
+ require file
171
+ rescue LoadError, StandardError => e
172
+ warn "[ActiveCypher] Failed to load #{file}: #{e.message}"
173
+ end
174
+ end
175
+ end
176
+
177
+ # Get the file path for a model
178
+ def model_file_path(model)
179
+ # Try const_source_location first (Ruby 2.7+)
180
+ if model.respond_to?(:const_source_location)
181
+ location = Object.const_source_location(model.name)
182
+ return location&.first
183
+ end
184
+
185
+ # Fallback: try to find via instance method
186
+ if model.instance_methods(false).any?
187
+ method = model.instance_method(model.instance_methods(false).first)
188
+ return method.source_location&.first
189
+ end
190
+
191
+ nil
192
+ rescue StandardError
193
+ nil
194
+ end
195
+
196
+ # Write annotation to file using RailsLens FileInsertionHelper
197
+ def write_annotation(file_path, model, annotation)
198
+ annotation_block = build_annotation_block(annotation)
199
+
200
+ ::RailsLens::FileInsertionHelper.insert_at_class_definition(
201
+ file_path,
202
+ model.name.split('::').last,
203
+ annotation_block
204
+ )
205
+ end
206
+
207
+ # Build the annotation block with markers
208
+ def build_annotation_block(annotation)
209
+ lines = [ANNOTATION_BEGIN]
210
+ annotation.each_line do |line|
211
+ content = line.chomp
212
+ lines << (content.empty? ? '#' : "# #{content}")
213
+ end
214
+ lines << ANNOTATION_END
215
+ lines.join("\n")
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,409 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RailsLens extension for ActiveCypher graph models
4
+ # Provides annotation support for Node and Relationship classes
5
+ #
6
+ # This extension detects ActiveCypher models and generates annotations
7
+ # including labels, attributes, associations, and relationship metadata.
8
+
9
+ begin
10
+ require 'rails_lens/extensions/base'
11
+ rescue LoadError
12
+ # RailsLens not available - define a stub Base class
13
+ module RailsLens
14
+ module Extensions
15
+ class Base
16
+ INTERFACE_VERSION = '1.0'
17
+
18
+ class << self
19
+ def gem_name = raise(NotImplementedError)
20
+ def detect? = raise(NotImplementedError)
21
+ def interface_version = INTERFACE_VERSION
22
+ def compatible? = true
23
+
24
+ def gem_available?(name)
25
+ Gem::Specification.find_by_name(name)
26
+ true
27
+ rescue Gem::LoadError
28
+ false
29
+ end
30
+ end
31
+
32
+ attr_reader :model_class
33
+
34
+ def initialize(model_class)
35
+ @model_class = model_class
36
+ end
37
+
38
+ def annotate = nil
39
+ def notes = []
40
+ def erd_additions = { relationships: [], badges: [], attributes: {} }
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ module ActiveCypher
47
+ # RailsLens extension module for annotating ActiveCypher graph models
48
+ #
49
+ # Detects and annotates:
50
+ # - Node classes (inheriting from ActiveCypher::Base)
51
+ # - Relationship classes (inheriting from ActiveCypher::Relationship)
52
+ #
53
+ # Generates annotations for:
54
+ # - Graph labels
55
+ # - Attributes with types
56
+ # - Associations (has_many, belongs_to, has_one)
57
+ # - Relationship endpoints and types
58
+ # - Connection configuration
59
+ module RailsLensExt
60
+ class Extension < ::RailsLens::Extensions::Base
61
+ INTERFACE_VERSION = '1.0'
62
+
63
+ class << self
64
+ def gem_name
65
+ 'activecypher'
66
+ end
67
+
68
+ def detect?
69
+ return false unless gem_available?(gem_name)
70
+
71
+ # Ensure ActiveCypher is loaded
72
+ require 'activecypher' unless defined?(::ActiveCypher::Base)
73
+ true
74
+ rescue LoadError
75
+ false
76
+ end
77
+ end
78
+
79
+ # Generate annotation string for ActiveCypher models
80
+ def annotate
81
+ return nil unless active_cypher_model?
82
+
83
+ lines = []
84
+
85
+ if node_class?
86
+ lines.concat(node_annotation_lines)
87
+ elsif relationship_class?
88
+ lines.concat(relationship_annotation_lines)
89
+ end
90
+
91
+ return nil if lines.empty?
92
+
93
+ lines.join("\n")
94
+ end
95
+
96
+ # Generate analysis notes for best practices
97
+ def notes
98
+ return [] unless active_cypher_model?
99
+
100
+ notes = []
101
+
102
+ if node_class?
103
+ notes.concat(node_notes)
104
+ elsif relationship_class?
105
+ notes.concat(relationship_notes)
106
+ end
107
+
108
+ notes
109
+ end
110
+
111
+ # Generate ERD additions for graph visualization
112
+ def erd_additions
113
+ return default_erd_additions unless active_cypher_model?
114
+
115
+ if node_class?
116
+ node_erd_additions
117
+ elsif relationship_class?
118
+ relationship_erd_additions
119
+ else
120
+ default_erd_additions
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def default_erd_additions
127
+ { relationships: [], badges: [], attributes: {} }
128
+ end
129
+
130
+ # ============================================================
131
+ # Detection Methods
132
+ # ============================================================
133
+
134
+ def active_cypher_model?
135
+ node_class? || relationship_class?
136
+ end
137
+
138
+ def node_class?
139
+ return false unless defined?(::ActiveCypher::Base)
140
+
141
+ model_class < ::ActiveCypher::Base
142
+ rescue StandardError
143
+ false
144
+ end
145
+
146
+ def relationship_class?
147
+ return false unless defined?(::ActiveCypher::Relationship)
148
+
149
+ model_class < ::ActiveCypher::Relationship
150
+ rescue StandardError
151
+ false
152
+ end
153
+
154
+ def abstract_class?
155
+ model_class.respond_to?(:abstract_class?) && model_class.abstract_class?
156
+ end
157
+
158
+ # ============================================================
159
+ # Node Annotation (TOML format)
160
+ # ============================================================
161
+
162
+ def node_annotation_lines
163
+ lines = []
164
+ lines << 'model_type = "node"'
165
+ lines << 'abstract = true' if abstract_class?
166
+
167
+ # Labels
168
+ if model_class.respond_to?(:labels) && model_class.labels.any?
169
+ labels = model_class.labels.map { |l| "\"#{l}\"" }.join(', ')
170
+ lines << "labels = [#{labels}]"
171
+ end
172
+
173
+ # Attributes
174
+ lines.concat(attribute_lines)
175
+
176
+ # Associations
177
+ lines.concat(association_lines) if model_class.respond_to?(:_reflections)
178
+
179
+ # Connection info
180
+ lines.concat(connection_lines)
181
+
182
+ lines
183
+ end
184
+
185
+ # ============================================================
186
+ # Relationship Annotation (TOML format)
187
+ # ============================================================
188
+
189
+ def relationship_annotation_lines
190
+ lines = []
191
+ lines << 'model_type = "relationship"'
192
+ lines << 'abstract = true' if abstract_class?
193
+
194
+ # Relationship type
195
+ lines << "type = \"#{model_class.relationship_type}\"" if model_class.respond_to?(:relationship_type) && model_class.relationship_type
196
+
197
+ # Endpoints
198
+ lines << "from_class = \"#{model_class.from_class_name}\"" if model_class.respond_to?(:from_class_name) && model_class.from_class_name
199
+
200
+ lines << "to_class = \"#{model_class.to_class_name}\"" if model_class.respond_to?(:to_class_name) && model_class.to_class_name
201
+
202
+ # Node base class (for connection delegation)
203
+ lines << "node_base_class = \"#{model_class._node_base_class.name}\"" if model_class.respond_to?(:node_base_class) && model_class._node_base_class
204
+
205
+ # Attributes
206
+ lines.concat(attribute_lines)
207
+
208
+ # Connection info
209
+ lines.concat(connection_lines)
210
+
211
+ lines
212
+ end
213
+
214
+ # ============================================================
215
+ # Shared Annotation Helpers (TOML format)
216
+ # ============================================================
217
+
218
+ def attribute_lines
219
+ lines = []
220
+
221
+ return lines unless model_class.respond_to?(:attribute_types)
222
+
223
+ attrs = model_class.attribute_types.except('internal_id')
224
+ return lines if attrs.empty?
225
+
226
+ lines << ''
227
+ attr_entries = attrs.map do |name, type|
228
+ type_name = type.class.name.demodulize.underscore.sub(/_type$/, '')
229
+ "{ name = \"#{name}\", type = \"#{type_name}\" }"
230
+ end
231
+ lines << "attributes = [#{attr_entries.join(', ')}]"
232
+
233
+ lines
234
+ end
235
+
236
+ def association_lines
237
+ lines = []
238
+ reflections = model_class._reflections
239
+
240
+ return lines if reflections.empty?
241
+
242
+ lines << ''
243
+ lines << '[associations]'
244
+
245
+ reflections.each do |name, opts|
246
+ macro = opts[:macro]
247
+ target = opts[:class_name]
248
+ rel_type = opts[:relationship]
249
+ direction = opts[:direction]
250
+
251
+ parts = ["macro = \"#{macro}\""]
252
+ parts << "class = \"#{target}\"" if target
253
+ parts << "rel = \"#{rel_type}\"" if rel_type
254
+ parts << "direction = \"#{direction}\"" if direction && direction != :out
255
+
256
+ if opts[:through]
257
+ parts << "through = \"#{opts[:through]}\""
258
+ parts << "source = \"#{opts[:source]}\"" if opts[:source]
259
+ end
260
+
261
+ parts << "relationship_class = \"#{opts[:relationship_class]}\"" if opts[:relationship_class]
262
+
263
+ lines << "#{name} = { #{parts.join(', ')} }"
264
+ end
265
+
266
+ lines
267
+ end
268
+
269
+ def connection_lines
270
+ lines = []
271
+
272
+ return lines unless model_class.respond_to?(:connects_to_mappings)
273
+
274
+ mappings = model_class.connects_to_mappings
275
+ return lines if mappings.nil? || mappings.empty?
276
+
277
+ lines << ''
278
+ lines << '[connection]'
279
+
280
+ mappings.each do |role, db_key|
281
+ lines << "#{role} = \"#{db_key}\""
282
+ end
283
+
284
+ lines
285
+ end
286
+
287
+ # ============================================================
288
+ # Node Notes (Best Practices)
289
+ # ============================================================
290
+
291
+ def node_notes
292
+ notes = []
293
+
294
+ # Check for missing labels
295
+ notes << "[activecypher] #{model_class.name}: No labels defined" if model_class.respond_to?(:labels) && model_class.labels.empty?
296
+
297
+ # Check for models without attributes (besides internal_id)
298
+ if model_class.respond_to?(:attribute_types)
299
+ user_attrs = model_class.attribute_types.except('internal_id')
300
+ notes << "[activecypher] #{model_class.name}: No attributes defined" if user_attrs.empty? && !abstract_class?
301
+ end
302
+
303
+ # Check for potential N+1 patterns in associations
304
+ if model_class.respond_to?(:_reflections)
305
+ has_many_count = model_class._reflections.count { |_, r| r[:macro] == :has_many }
306
+ notes << "[activecypher] #{model_class.name}: #{has_many_count} has_many associations - consider eager loading" if has_many_count > 3
307
+ end
308
+
309
+ notes
310
+ end
311
+
312
+ # ============================================================
313
+ # Relationship Notes (Best Practices)
314
+ # ============================================================
315
+
316
+ def relationship_notes
317
+ notes = []
318
+
319
+ # Check for missing endpoints
320
+ unless abstract_class?
321
+ if !model_class.respond_to?(:from_class_name) || model_class.from_class_name.nil?
322
+ notes << "[activecypher] #{model_class.name}: Missing from_class definition"
323
+ end
324
+
325
+ if !model_class.respond_to?(:to_class_name) || model_class.to_class_name.nil?
326
+ notes << "[activecypher] #{model_class.name}: Missing to_class definition"
327
+ end
328
+
329
+ if !model_class.respond_to?(:relationship_type) || model_class.relationship_type.nil?
330
+ notes << "[activecypher] #{model_class.name}: Missing relationship type definition"
331
+ end
332
+ end
333
+
334
+ notes
335
+ end
336
+
337
+ # ============================================================
338
+ # ERD Additions
339
+ # ============================================================
340
+
341
+ def node_erd_additions
342
+ badges = ['graph-node']
343
+ badges << 'abstract' if abstract_class?
344
+
345
+ relationships = []
346
+
347
+ # Add relationships from associations
348
+ if model_class.respond_to?(:_reflections)
349
+ model_class._reflections.each_value do |opts|
350
+ rel = {
351
+ type: opts[:macro].to_s,
352
+ from: model_class.name,
353
+ to: opts[:class_name],
354
+ label: opts[:relationship],
355
+ style: opts[:macro] == :has_many ? 'solid' : 'dashed',
356
+ direction: opts[:direction]
357
+ }
358
+ relationships << rel
359
+ end
360
+ end
361
+
362
+ {
363
+ relationships: relationships,
364
+ badges: badges,
365
+ attributes: {
366
+ model_type: 'node',
367
+ labels: model_class.respond_to?(:labels) ? model_class.labels : []
368
+ }
369
+ }
370
+ end
371
+
372
+ def relationship_erd_additions
373
+ badges = ['graph-relationship']
374
+ badges << 'abstract' if abstract_class?
375
+
376
+ relationships = []
377
+
378
+ # Add the relationship edge
379
+ if model_class.respond_to?(:from_class_name) &&
380
+ model_class.respond_to?(:to_class_name) &&
381
+ model_class.from_class_name &&
382
+ model_class.to_class_name
383
+
384
+ relationships << {
385
+ type: 'edge',
386
+ from: model_class.from_class_name,
387
+ to: model_class.to_class_name,
388
+ label: model_class.relationship_type || 'RELATED',
389
+ style: 'bold',
390
+ model: model_class.name
391
+ }
392
+ end
393
+
394
+ {
395
+ relationships: relationships,
396
+ badges: badges,
397
+ attributes: {
398
+ model_type: 'relationship',
399
+ relationship_type: model_class.respond_to?(:relationship_type) ? model_class.relationship_type : nil
400
+ }
401
+ }
402
+ end
403
+ end
404
+ end
405
+
406
+ # Register the extension with RailsLens for gem-based auto-discovery
407
+ # RailsLens looks for GemName::RailsLensExtension constant
408
+ RailsLensExtension = RailsLensExt::Extension
409
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Only define model source if RailsLens is available
4
+ # This file is loaded conditionally via railtie, not via autoload
5
+ return unless defined?(RailsLens::ModelSource)
6
+
7
+ require_relative 'annotator'
8
+
9
+ module ActiveCypher
10
+ module RailsLensExt
11
+ # Model source for ActiveCypher graph models
12
+ # Provides integration with RailsLens annotation system
13
+ class ModelSource < ::RailsLens::ModelSource
14
+ class << self
15
+ def models(options = {})
16
+ Annotator.send(:discover_models, options)
17
+ end
18
+
19
+ def file_patterns
20
+ ['app/graph/**/*.rb', 'app/models/graph/**/*.rb', 'app/graphs/**/*.rb']
21
+ end
22
+
23
+ def annotate_model(model, options = {})
24
+ Annotator.annotate_model(model, options)
25
+ end
26
+
27
+ def remove_annotation(model)
28
+ Annotator.remove_annotation(model)
29
+ end
30
+
31
+ def source_name
32
+ 'ActiveCypher Graph'
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ # Register for auto-discovery by RailsLens (for gems with conventional names)
39
+ RailsLensModelSource = RailsLensExt::ModelSource
40
+
41
+ # Explicitly register with RailsLens (gem name 'activecypher' doesn't match 'ActiveCypher')
42
+ ::RailsLens::ModelSourceLoader.register(RailsLensExt::ModelSource)
43
+ end
@@ -60,6 +60,11 @@ module ActiveCypher
60
60
  end
61
61
  end
62
62
 
63
+ # Load RailsLens integration if RailsLens is available
64
+ initializer 'active_cypher.rails_lens_integration', after: :load_config_initializers do
65
+ require 'active_cypher/rails_lens_ext/model_source' if defined?(::RailsLens::ModelSource)
66
+ end
67
+
63
68
  generators do
64
69
  require 'active_cypher/generators/install_generator'
65
70
  require 'active_cypher/generators/node_generator'
@@ -70,6 +75,7 @@ module ActiveCypher
70
75
  rake_tasks do
71
76
  load File.expand_path('../tasks/graphdb_migrate.rake', __dir__)
72
77
  load File.expand_path('../tasks/graphdb_schema.rake', __dir__)
78
+ # Standalone annotation task no longer needed - RailsLens handles it
73
79
  end
74
80
  end
75
81
  end
@@ -129,6 +129,10 @@ module ActiveCypher
129
129
  # Prevent subclasses from overriding node_base_class
130
130
  def inherited(subclass)
131
131
  super
132
+ # Reset abstract_class for subclasses (mirrors Model::Abstract behavior
133
+ # which gets overridden by this method definition)
134
+ subclass.abstract_class = false
135
+
132
136
  return unless _node_base_class
133
137
 
134
138
  subclass._node_base_class = _node_base_class
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveCypher
4
- VERSION = '0.12.2'
4
+ VERSION = '0.13.0'
5
5
 
6
6
  def self.gem_version
7
7
  Gem::Version.new VERSION
data/lib/activecypher.rb CHANGED
@@ -100,6 +100,7 @@ loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
100
100
  loader.ignore("#{__dir__}/active_cypher/version.rb")
101
101
  loader.ignore("#{__dir__}/active_cypher/railtie.rb")
102
102
  loader.ignore("#{__dir__}/active_cypher/generators")
103
+ loader.ignore("#{__dir__}/active_cypher/rails_lens_ext")
103
104
  loader.ignore("#{__dir__}/activecypher.rb")
104
105
  loader.ignore("#{__dir__}/cyrel.rb")
105
106
  loader.inflector.inflect(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activecypher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.2
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -107,6 +107,20 @@ dependencies:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rails_lens
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
110
124
  description: OpenCypher Adapter ala ActiveRecord
111
125
  email:
112
126
  - seuros@pre-history.com
@@ -171,6 +185,9 @@ files:
171
185
  - lib/active_cypher/model/labelling.rb
172
186
  - lib/active_cypher/model/persistence.rb
173
187
  - lib/active_cypher/model/querying.rb
188
+ - lib/active_cypher/rails_lens_ext/annotator.rb
189
+ - lib/active_cypher/rails_lens_ext/extension.rb
190
+ - lib/active_cypher/rails_lens_ext/model_source.rb
174
191
  - lib/active_cypher/railtie.rb
175
192
  - lib/active_cypher/redaction.rb
176
193
  - lib/active_cypher/relation.rb