rails_age 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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