rails_age 0.3.1 → 0.4.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.
@@ -0,0 +1,56 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/named_base'
3
+
4
+ module ApacheAge
5
+ class EdgeGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
8
+
9
+ def perform_task
10
+ behavior == :invoke ? create_edge_file : destroy_edge_file
11
+ end
12
+
13
+ private
14
+
15
+ def create_edge_file
16
+ template "edge.rb.tt", File.join("app/edge", class_path, "#{file_name}.rb")
17
+ end
18
+
19
+ def destroy_edge_file
20
+ file_path = File.join("app/edge", class_path, "#{file_name}.rb")
21
+ File.delete(file_path) if File.exist?(file_path)
22
+ end
23
+
24
+ def attributes_list
25
+ attributes.map { |attr| { name: attr.name, type: attr.type } }
26
+ end
27
+
28
+ def unique_attributes
29
+ attributes_list.map { |attr| attr[:name].to_sym }
30
+ end
31
+
32
+ def parent_module
33
+ class_path.map(&:camelize).join('::')
34
+ end
35
+
36
+ def full_class_name
37
+ parent_module.empty? ? class_name : "#{parent_module}::#{class_name}"
38
+ end
39
+
40
+ def indented_namespace
41
+ return '' if parent_module.empty?
42
+
43
+ parent_module.split('::').map.with_index do |namespace, index|
44
+ "#{' ' * index}module #{namespace}"
45
+ end.join("\n") + "\n"
46
+ end
47
+
48
+ def indented_end_namespace
49
+ return '' if parent_module.empty?
50
+
51
+ parent_module.split('::').map.with_index do |_, index|
52
+ "#{' ' * (parent_module.split('::').length - 1 - index)}end"
53
+ end.join("\n") + "\n"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,20 @@
1
+ <%= indented_namespace %>
2
+ <%= ' ' * class_path.length %>class <%= class_name %>
3
+ <%= ' ' * class_path.length %> include ApacheAge::Entities::Edge
4
+
5
+ <%- attributes_list.each do |attribute| -%>
6
+ <%= ' ' * class_path.length %> attribute :<%= attribute[:name] %>, :<%= attribute[:reference] || attribute[:type] %>
7
+ <%- end -%>
8
+
9
+ <%= ' ' * class_path.length %> validates :<%= unique_attributes.first %>, presence: true
10
+ <%= ' ' * class_path.length %> validate :validate_unique
11
+
12
+ <%= ' ' * class_path.length %> private
13
+
14
+ <%= ' ' * class_path.length %> def validate_unique
15
+ <%= ' ' * class_path.length %> ApacheAge::Validators::UniqueEdgeValidator
16
+ <%= ' ' * class_path.length %> .new(attributes: <%= unique_attributes.inspect %>)
17
+ <%= ' ' * class_path.length %> .validate(self)
18
+ <%= ' ' * class_path.length %> end
19
+ <%= ' ' * class_path.length %>end
20
+ <%= indented_end_namespace %>
@@ -0,0 +1,54 @@
1
+ Description:
2
+ This creates Apache AGE nodes that work seamlessly with Rails.
3
+ A node can be created with or without a namespace.
4
+ See the below examples.
5
+
6
+ Example:
7
+ `bin/rails g apache_age:node Cat name age:integer`
8
+
9
+ This creates:
10
+ `app/nodes/cat.rb`
11
+
12
+ with the contents:
13
+ ```
14
+ class Cat
15
+ include ApacheAge::Entities::Vertex
16
+
17
+ attribute :name, :string
18
+ attribute :age, :integer
19
+
20
+ validates :name, presence: true
21
+ validates :age, presence: true
22
+
23
+ # unique node validator (remove any attributes that are not important to uniqueness)
24
+ validates_with(
25
+ ApacheAge::Validators::UniqueVertexValidator,
26
+ attributes: [:name, :age]
27
+ )
28
+ end
29
+ ```
30
+
31
+ A namespace can also be used:
32
+ `bin/rails g apache_age:node Animals/Cat name age:integer`
33
+
34
+ This creates:
35
+ `app/nodes/animals/cat.rb`
36
+
37
+ with the contents
38
+ ```
39
+ class Animals::Cat
40
+ include ApacheAge::Entities::Vertex
41
+
42
+ attribute :name, :string
43
+ attribute :age, :integer
44
+
45
+ validates :name, presence: true
46
+ validates :age, presence: true
47
+
48
+ # unique node validator (remove any attributes that are not important to uniqueness)
49
+ validates_with(
50
+ ApacheAge::Validators::UniqueVertexValidator,
51
+ attributes: [:name, :age]
52
+ )
53
+ end
54
+ ```
@@ -0,0 +1,94 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/named_base'
3
+
4
+ module ApacheAge
5
+ class NodeGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
8
+
9
+ def perform_task
10
+ behavior == :invoke ? create_node_file : destroy_node_file
11
+ end
12
+
13
+ private
14
+
15
+ def create_node_file
16
+ template "node.rb.tt", File.join(destination_root, "app/nodes", class_path, "#{file_name}.rb")
17
+ add_type_config
18
+ end
19
+
20
+ def destroy_node_file
21
+ file_path = File.join(destination_root, "app/nodes", class_path, "#{file_name}.rb")
22
+ File.delete(file_path) if File.exist?(file_path)
23
+ remove_type_config
24
+ end
25
+
26
+ def attributes_list
27
+ attributes.map { |attr| { name: attr.name, type: attr.type } }
28
+ end
29
+
30
+ def unique_attributes
31
+ attributes_list.map { |attr| attr[:name].to_sym }
32
+ end
33
+
34
+ def parent_module
35
+ class_path.map(&:camelize).join('::')
36
+ end
37
+
38
+ def full_class_name
39
+ parent_module.empty? ? class_name : "#{parent_module}::#{class_name}"
40
+ end
41
+
42
+ def indented_namespace
43
+ return '' if parent_module.empty?
44
+
45
+ parent_module.split('::').map.with_index do |namespace, index|
46
+ "#{' ' * index}module #{namespace}"
47
+ end.join("\n") + "\n"
48
+ end
49
+
50
+ def indented_end_namespace
51
+ return '' if parent_module.empty?
52
+
53
+ parent_module.split('::').map.with_index do |_, index|
54
+ "#{' ' * (parent_module.split('::').length - 1 - index)}end"
55
+ end.join("\n") + "\n"
56
+ end
57
+
58
+ def add_type_config
59
+ return unless File.exist?(types_config_file)
60
+
61
+ types_content = File.read(types_config_file)
62
+ types_content.sub!(/^end\s*$/, "#{new_type_content}end")
63
+ File.open(types_config_file, 'w') { |file| file.write(types_content) }
64
+ puts " modified: config/initializers/types.rb"
65
+ end
66
+
67
+ def remove_type_config
68
+ return unless File.exist?(types_config_file)
69
+
70
+ type_to_remove = new_type_content
71
+
72
+ types_content = File.read(types_config_file)
73
+ types_content.gsub!(type_to_remove, '')
74
+ File.open(types_config_file, 'w') { |file| file.write(types_content) }
75
+ end
76
+
77
+ def types_config_file = File.join(Rails.root, 'config/initializers/types.rb')
78
+
79
+ def new_type_content
80
+ file_path = [class_path, file_name].reject(&:blank?).join('/').downcase
81
+ node_namespace = class_path.map(&:capitalize).join('::')
82
+ node_class_name = file_name.split('_').map(&:capitalize).join
83
+ node_namespaced_class = [node_namespace, node_class_name].reject(&:blank?).join('::')
84
+ type_name = [class_path.join('_'), file_name].reject(&:blank?).join('_')
85
+ content =
86
+ <<-RUBY
87
+ require_dependency '#{file_path}'
88
+ ActiveModel::Type.register(
89
+ :#{type_name}, ApacheAge::Types::AgeTypeGenerator.create_type_for(#{node_namespaced_class})
90
+ )
91
+ RUBY
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,17 @@
1
+ class <%= class_name %>
2
+ include ApacheAge::Entities::Vertex
3
+
4
+ <%- attributes_list.each do |attribute| -%>
5
+ attribute :<%= attribute[:name] %>, :<%= attribute[:type] %>
6
+ <%- end -%>
7
+
8
+ <%- attributes_list.each do |attribute| -%>
9
+ validates :<%= attribute[:name] %>, presence: true
10
+ <%- end -%>
11
+
12
+ # unique node validator (remove any attributes that are not important to uniqueness)
13
+ validates_with(
14
+ ApacheAge::Validators::UniqueVertexValidator,
15
+ attributes: <%= unique_attributes.inspect %>
16
+ )
17
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsAge
2
- VERSION = '0.3.1'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  namespace :apache_age do
5
5
  desc "Ensure the database.yml file is properly configured for Apache Age"
6
- task :database_config => :environment do
6
+ task :config_database => :environment do
7
7
 
8
8
  db_config_file = File.expand_path("#{Rails.root}/config/database.yml", __FILE__)
9
9
 
@@ -0,0 +1,35 @@
1
+ # namespace :apache_age do
2
+ # desc "Ensure db:migrate is followed by apache_age:config_schema"
3
+ # task :config_migrate do
4
+ # bin_rails_path = File.expand_path('../../../bin/rails', __FILE__)
5
+ # migration_hook =
6
+ # <<-RUBY
7
+ # # ensure db:migrate is followed with: `Rake::Task["apache_age:config_schema"].invoke`
8
+ # # which repairs the schema.rb file after the migration mangles it
9
+ # require_relative '../config/boot'
10
+ # require 'rails/commands'
11
+
12
+ # if ARGV.first == 'db:migrate'
13
+ # require 'rake'
14
+ # Rails.application.load_tasks
15
+ # Rake::Task['db:migrate'].invoke
16
+ # Rake::Task["apache_age:config_schema"].invoke
17
+ # else
18
+ # Rake::Task['rails:commands'].invoke(*ARGV)
19
+ # end
20
+ # RUBY
21
+
22
+ # # Read the current content of the bin/rails file
23
+ # bin_rails_content = File.read(bin_rails_path)
24
+
25
+ # # Check if the migration hook is already present
26
+ # unless bin_rails_content.include?('Rake::Task["apache_age:config_schema"].invoke')
27
+ # # Append the migration hook to the end of the bin/rails file
28
+ # File.open(bin_rails_path, 'a') do |file|
29
+ # file.write(migration_hook)
30
+ # end
31
+ # puts "Migration hook added to bin/rails."
32
+ # else
33
+ # puts "Migration hook already present in bin/rails."
34
+ # end
35
+ # end
@@ -3,8 +3,7 @@
3
3
  #
4
4
  namespace :apache_age do
5
5
  desc "Copy migrations from rails_age to application and update schema"
6
- task :schema_config => :environment do
7
- # source_schema = File.expand_path('../../../db/schema.rb', __FILE__)
6
+ task :config_schema => :environment do
8
7
  destination_schema = File.expand_path("#{Rails.root}/db/schema.rb", __FILE__)
9
8
 
10
9
  unless File.exist?(destination_schema)
@@ -0,0 +1,85 @@
1
+ # lib/tasks/install.rake
2
+ # Usage: `rake apache_age:config_types`
3
+ #
4
+ namespace :apache_age do
5
+ desc "Install AGE types from rails_age into the rails initializers"
6
+ task :config_types => :environment do
7
+ types_file_path = File.expand_path("#{Rails.root}/config/initializers/types.rb", __FILE__)
8
+ required_file_path = "require 'apache_age/types/age_type_generator'"
9
+ required_file_content =
10
+ <<~RUBY
11
+ require 'apache_age/types/age_type_generator'
12
+ # AGE Type Definition Usage (edges/nodes):
13
+ # require_dependency 'nodes/company'
14
+ # ActiveModel::Type.register(
15
+ # :company, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Company)
16
+ # )
17
+ RUBY
18
+ node_type_content =
19
+ <<-RUBY
20
+ require_dependency 'apache_age/entities/vertex'
21
+ ActiveModel::Type.register(
22
+ :vertex, ApacheAge::Types::AgeTypeGenerator.create_type_for(ApacheAge::Entities::Vertex)
23
+ )
24
+ RUBY
25
+ edge_type_content =
26
+ <<-RUBY
27
+ require_dependency 'apache_age/entities/edge'
28
+ ActiveModel::Type.register(
29
+ :edge, ApacheAge::Types::AgeTypeGenerator.create_type_for(ApacheAge::Entities::Edge)
30
+ )
31
+ RUBY
32
+
33
+ unless File.exist?(types_file_path)
34
+ source_content =
35
+ <<~RUBY
36
+ # config/initializers/types.rb
37
+
38
+ #{required_file_content}
39
+ Rails.application.config.to_prepare do
40
+ # Register AGE types
41
+ #{node_type_content}
42
+ #{edge_type_content}
43
+ end
44
+ RUBY
45
+ File.open(types_file_path, 'w') { |file| file.write(source_content) }
46
+ puts "config/initializers/types.rb file created with AGE base types"
47
+ else
48
+ destination_content = File.read(types_file_path)
49
+ original_content = destination_content.dup
50
+
51
+ unless destination_content.include?(required_file_path)
52
+ destination_content.sub!(
53
+ /^(\s*Rails\.application\.config\.to_prepare do\n)/,
54
+ "#{required_file_content}\n\\1"
55
+ )
56
+ end
57
+
58
+ unless destination_content.include?('# Register AGE types')
59
+ destination_content.sub!(
60
+ /^(\s*Rails\.application\.config\.to_prepare do\n)/,
61
+ "\\1 # Register AGE types\n"
62
+ )
63
+ end
64
+
65
+ unless destination_content.include?(edge_type_content)
66
+ destination_content.sub!(
67
+ /^(\s*Rails\.application\.config\.to_prepare do\n # Register AGE types\n)/,
68
+ "\\1#{edge_type_content}"
69
+ )
70
+ end
71
+
72
+ unless destination_content.include?(node_type_content)
73
+ destination_content.sub!(
74
+ /^(\s*Rails\.application\.config\.to_prepare do\n # Register AGE types\n)/,
75
+ "\\1#{node_type_content}"
76
+ )
77
+ end
78
+
79
+ if destination_content != original_content
80
+ File.open(types_file_path, 'w') { |file| file.write(destination_content) }
81
+ puts "modified: config/initializers/types.rb"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -10,10 +10,17 @@ namespace :apache_age do
10
10
  # run any new migrations
11
11
  Rake::Task["db:migrate"].invoke
12
12
 
13
+ # ensure the config/database.yml file has the proper configurations
14
+ Rake::Task["apache_age:config_database"].invoke
15
+
13
16
  # adjust the schema file (unfortunately rails mangles the schema file)
14
- Rake::Task["apache_age:schema_config"].invoke
17
+ Rake::Task["apache_age:config_schema"].invoke
15
18
 
16
- # ensure the config/database.yml file has the proper configurations
17
- Rake::Task["apache_age:database_config"].invoke
19
+ # ensure the config/initializers/types.rb file has the base AGE Types
20
+ Rake::Task["apache_age:config_types"].invoke
21
+
22
+ # # ensure bin/rails db:migrate is always followed with
23
+ # # `Rake::Task["apache_age:config_schema"].invoke` to ensure the schema isn't mangled
24
+ # Rake::Task["apache_age:config_migrate"].invoke
18
25
  end
19
26
  end
@@ -0,0 +1,7 @@
1
+ namespace :apache_age do
2
+ desc "Ensure 'db:migrate' is followed by 'apache_age:config_schema' to repair 'schema.rb' after migrations"
3
+ task :migrate do
4
+ Rake::Task['db:migrate'].invoke
5
+ Rake::Task["apache_age:config_schema"].invoke
6
+ end
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_age
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bill Tihen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-02 00:00:00.000000000 Z
11
+ date: 2024-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -76,14 +76,22 @@ files:
76
76
  - lib/apache_age/validators/unique_edge_validator.rb
77
77
  - lib/apache_age/validators/unique_vertex_validator.rb
78
78
  - lib/apache_age/validators/vertex_type_validator.rb
79
+ - lib/generators/apache_age/edge/USAGE
80
+ - lib/generators/apache_age/edge/edge_generator.rb
81
+ - lib/generators/apache_age/edge/templates/edge.rb.tt
82
+ - lib/generators/apache_age/node/USAGE
83
+ - lib/generators/apache_age/node/node_generator.rb
84
+ - lib/generators/apache_age/node/templates/node.rb.tt
79
85
  - lib/rails_age.rb
80
86
  - lib/rails_age/engine.rb
81
87
  - lib/rails_age/version.rb
88
+ - lib/tasks/config_database.rake
89
+ - lib/tasks/config_migrate.rake
90
+ - lib/tasks/config_schema.rake
91
+ - lib/tasks/config_types.rake
82
92
  - lib/tasks/copy_migrations.rake
83
- - lib/tasks/database_config.rake
84
- - lib/tasks/install.original.rake
85
93
  - lib/tasks/install.rake
86
- - lib/tasks/schema_config.rake
94
+ - lib/tasks/migrate.rake
87
95
  homepage: https://github.com/marpori/rails_age
88
96
  licenses:
89
97
  - MIT