advanced_relationship_management 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8f221df65f0856c3c8b946e80ecda01dd2a32557497ea66d9e3bee45e9fe7b24
4
+ data.tar.gz: e32baec528fab08a83c1f84cec9b844e65fc76e7ee7bea63d5e5100f778edc7e
5
+ SHA512:
6
+ metadata.gz: 513d6327b555cde3e42101a92ddf79e8fd0b7fa00edc3e190c042733b59ac6c2d9257c76b690b430fe2b39cd672a302fc4e8662bfb00041d34f87464823ebda3
7
+ data.tar.gz: c9a7f3608959234358bde548208b5541cb2d8f82aac5c8e5650131e0a6743473073a76cb22d89c61aa52f97ffd22b5e74802ead2995837e575a5d6b10fbb62bb
@@ -0,0 +1,25 @@
1
+ module AdvancedRelationshipManagement
2
+ module AdvancedScoping
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ scope :roots, -> { where(parent_id: nil) }
7
+ scope :by_depth, ->(depth) { where("depth <= ?", depth) }
8
+
9
+ def depth
10
+ if AdvancedRelationshipManagement.enable_caching
11
+ Rails.cache.fetch([self, "depth"]) { calculate_depth }
12
+ else
13
+ calculate_depth
14
+ end
15
+ end
16
+
17
+ def calculate_depth
18
+ ancestors.size
19
+ end
20
+
21
+ scope :with_min_descendants, ->(min) { select { |record| record.descendants.size >= min } }
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,16 @@
1
+ module AdvancedRelationshipManagement
2
+ module Configuration
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ mattr_accessor :default_parent_column, :default_child_column, :enable_caching
7
+ end
8
+
9
+ module ClassMethods
10
+ def configure
11
+ yield self
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,14 @@
1
+ module AdvancedRelationshipManagement
2
+ module Counts
3
+ extend ActiveSupport::Concern
4
+
5
+ def ancestor_count
6
+ ancestors.size
7
+ end
8
+
9
+ def descendant_count
10
+ descendants.size
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,16 @@
1
+ module AdvancedRelationshipManagement
2
+ module CycleDetection
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ validate :no_cycles
7
+ end
8
+
9
+ def no_cycles
10
+ if ancestors.include?(self)
11
+ errors.add(:base, "Cycle detected in the hierarchy")
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,12 @@
1
+ module AdvancedRelationshipManagement
2
+ module FilterScope
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def filter_scope(name, scope_block)
7
+ scope name, scope_block
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,19 @@
1
+ module AdvancedRelationshipManagement
2
+ module LazyLoading
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ scope :lazy_load_children, -> { includes(:children) }
7
+ scope :lazy_load_parent, -> { includes(:parent) }
8
+ end
9
+
10
+ def lazy_descendants
11
+ self.class.lazy_load_children.where("#{self.class.parent_column_name} = ?", self.id)
12
+ end
13
+
14
+ def lazy_ancestors
15
+ self.class.lazy_load_parent.where("#{self.class.child_column_name} = ?", self.id)
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,33 @@
1
+ module AdvancedRelationshipManagement
2
+ module PathToRoot
3
+ extend ActiveSupport::Concern
4
+
5
+ def path_to_root(format: :array, attribute: :id)
6
+ path = []
7
+ current_node = self
8
+ while current_node
9
+ path << current_node
10
+ current_node = current_node.parent
11
+ end
12
+ path.reverse!
13
+
14
+ case format
15
+ when :array
16
+ path
17
+ when :symbolic
18
+ path.map { |node| node.public_send(attribute) }.join(' -> ')
19
+ when :json
20
+ path.map { |node| { id: node.id, attribute => node.public_send(attribute) } }.to_json
21
+ when :html
22
+ path.map { |node| "<a href='/users/#{node.id}'>#{node.public_send(attribute)}</a>" }.join(' > ')
23
+ when :reverse_symbolic
24
+ path.map { |node| node.public_send(attribute) }.reverse.join(' -> ')
25
+ when :nested_hash
26
+ path.inject(nil) { |acc, node| { id: node.id, attribute => node.public_send(attribute), parent: acc } }
27
+ else
28
+ raise ArgumentError, "Unsupported format: #{format}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,7 @@
1
+ require 'rails/railtie'
2
+
3
+ module AdvancedRelationshipManagement
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :advanced_relationship_management
6
+ end
7
+ end
@@ -0,0 +1,98 @@
1
+ module AdvancedRelationshipManagement
2
+ module RecursiveRelationships
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ after_initialize :setup_recursive_relationships
7
+ after_create :clear_cache
8
+ after_update :clear_cache
9
+ after_destroy :clear_cache
10
+ end
11
+
12
+ def setup_recursive_relationships
13
+ return if self.class.reflect_on_association(:children) && self.class.reflect_on_association(:parent)
14
+
15
+ parent_column = self.class.parent_column_name
16
+ self.class.setup_relationships(parent_column)
17
+ end
18
+
19
+ def descendants(*scopes)
20
+ if AdvancedRelationshipManagement.enable_caching
21
+ Rails.cache.fetch([self, "descendants"] + scopes) { fetch_descendants(*scopes) }
22
+ else
23
+ fetch_descendants(*scopes)
24
+ end
25
+ end
26
+
27
+ def fetch_descendants(*scopes)
28
+ relation = self.class.find_by_sql([<<-SQL, id])
29
+ WITH RECURSIVE descendants_cte AS (
30
+ SELECT *
31
+ FROM #{self.class.table_name}
32
+ WHERE #{self.class.child_column_name} = ?
33
+ UNION ALL
34
+ SELECT #{self.class.table_name}.*
35
+ FROM #{self.class.table_name}
36
+ INNER JOIN descendants_cte ON descendants_cte.#{self.class.child_column_name} = #{self.class.table_name}.#{self.class.parent_column_name}
37
+ )
38
+ SELECT *
39
+ FROM descendants_cte
40
+ SQL
41
+
42
+ scopes.each do |scope|
43
+ relation = relation.public_send(scope) if scope.is_a?(Symbol)
44
+ relation = relation.merge(scope) if scope.is_a?(ActiveRecord::Relation)
45
+ end
46
+
47
+ relation
48
+ end
49
+
50
+ def ancestors
51
+ if AdvancedRelationshipManagement.enable_caching
52
+ Rails.cache.fetch([self, "ancestors"]) { fetch_ancestors }
53
+ else
54
+ fetch_ancestors
55
+ end
56
+ end
57
+
58
+ def fetch_ancestors
59
+ all_ancestors = []
60
+ ActiveRecord::Base.silence do
61
+ current_node = self
62
+ while current_node.parent
63
+ all_ancestors << current_node.parent
64
+ current_node = current_node.parent
65
+ end
66
+ end
67
+ all_ancestors
68
+ end
69
+
70
+ def depth_of_descendants
71
+ descendants_with_depth = {}
72
+ descendants.each do |descendant|
73
+ descendants_with_depth[descendant] = depth(descendant)
74
+ end
75
+ descendants_with_depth
76
+ end
77
+
78
+ def depth_of_ancestors
79
+ ancestors_with_depth = {}
80
+ ancestors.each do |ancestor|
81
+ ancestors_with_depth[ancestor] = depth(ancestor)
82
+ end
83
+ ancestors_with_depth
84
+ end
85
+
86
+ def depth(node)
87
+ node.ancestors.size
88
+ end
89
+
90
+ private
91
+
92
+ def clear_cache
93
+ Rails.cache.delete([self, "descendants"])
94
+ Rails.cache.delete([self, "ancestors"])
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,11 @@
1
+ module AdvancedRelationshipManagement
2
+ module RelationshipVisualizations
3
+ extend ActiveSupport::Concern
4
+
5
+ def visualize_relationships
6
+ # graph = graph()
7
+ # graph.write_to_graphic_file('png', 'relationship_graph')
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,12 @@
1
+ module AdvancedRelationshipManagement
2
+ module SiblingRelationships
3
+ extend ActiveSupport::Concern
4
+
5
+ def siblings
6
+ return [] if parent.nil?
7
+
8
+ parent.children.where.not(id: self.id)
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdvancedRelationshipManagement
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,88 @@
1
+ require 'active_support/concern'
2
+ require 'active_record'
3
+ require_relative 'advanced_relationship_management/recursive_relationships'
4
+ require_relative 'advanced_relationship_management/relationship_visualizations'
5
+ require_relative 'advanced_relationship_management/advanced_scoping'
6
+ require_relative 'advanced_relationship_management/cycle_detection'
7
+ require_relative 'advanced_relationship_management/sibling_relationships'
8
+ require_relative 'advanced_relationship_management/counts'
9
+ require_relative 'advanced_relationship_management/path_to_root'
10
+ require_relative 'advanced_relationship_management/filter_scope'
11
+ require_relative 'advanced_relationship_management/lazy_loading'
12
+
13
+ module AdvancedRelationshipManagement
14
+ extend ActiveSupport::Concern
15
+
16
+ mattr_accessor :default_parent_column, :default_child_column, :enable_caching
17
+
18
+ self.default_parent_column = :parent_id
19
+ self.default_child_column = :id
20
+ self.enable_caching = true
21
+
22
+ included do
23
+ include AdvancedRelationshipManagement::RecursiveRelationships
24
+ include AdvancedRelationshipManagement::RelationshipVisualizations
25
+ include AdvancedRelationshipManagement::AdvancedScoping
26
+ include AdvancedRelationshipManagement::CycleDetection
27
+ include AdvancedRelationshipManagement::SiblingRelationships
28
+ include AdvancedRelationshipManagement::Counts
29
+ include AdvancedRelationshipManagement::PathToRoot
30
+ include AdvancedRelationshipManagement::FilterScope
31
+ include AdvancedRelationshipManagement::LazyLoading
32
+ end
33
+
34
+ class_methods do
35
+ def setup_relationships(parent_column = nil, child_column = nil)
36
+ parent_column ||= AdvancedRelationshipManagement.default_parent_column
37
+ child_column ||= AdvancedRelationshipManagement.default_child_column
38
+
39
+ has_many :children, class_name: name, foreign_key: parent_column, inverse_of: :parent
40
+ belongs_to :parent, class_name: name, foreign_key: parent_column, optional: true
41
+ end
42
+
43
+ def configure_relationships(parent_column: :parent_id, child_column: :id, enable_caching: true)
44
+ @parent_column = parent_column
45
+ @child_column = child_column
46
+ AdvancedRelationshipManagement.enable_caching = enable_caching
47
+ setup_relationships(parent_column, child_column)
48
+ end
49
+
50
+ def parent_column(column_name)
51
+ @parent_column = column_name
52
+ setup_relationships(column_name, @child_column)
53
+ end
54
+
55
+ def child_column(column_name)
56
+ @child_column = column_name
57
+ setup_relationships(@parent_column, column_name)
58
+ end
59
+
60
+ def enable_caching?
61
+ AdvancedRelationshipManagement.enable_caching
62
+ end
63
+
64
+ def enable_caching(enable)
65
+ AdvancedRelationshipManagement.enable_caching = enable
66
+ end
67
+
68
+ def parent_column_name
69
+ @parent_column || AdvancedRelationshipManagement.default_parent_column
70
+ end
71
+
72
+ def child_column_name
73
+ @child_column || AdvancedRelationshipManagement.default_child_column
74
+ end
75
+ end
76
+ end
77
+
78
+ module ActiveRecord
79
+ class Base
80
+ def self.silence
81
+ old_logger = ActiveRecord::Base.logger
82
+ ActiveRecord::Base.logger = nil
83
+ yield
84
+ ensure
85
+ ActiveRecord::Base.logger = old_logger
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,23 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module AdvancedRelationshipManagement
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ def copy_migration
11
+ migration_template "create_graph_edges_migration.rb", "db/migrate/create_graph_edges.rb"
12
+ end
13
+
14
+ def self.next_migration_number(dirname)
15
+ if ActiveRecord::Base.timestamped_migrations
16
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
17
+ else
18
+ "%.3d" % (current_migration_number(dirname) + 1)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ class CreateGraphEdges < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :graph_edges do |t|
4
+ t.references :source, polymorphic: true, null: false
5
+ t.references :target, polymorphic: true, null: false
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,7 @@
1
+ namespace :advanced_relationship_management do
2
+ desc "Install AdvancedRelationshipManagement"
3
+ task install: :environment do
4
+ Rails::Generators.invoke("advanced_relationship_management:install")
5
+ end
6
+ end
7
+
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: advanced_relationship_management
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jana
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rgl
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.5
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.5.5
41
+ description: A gem to manage complex relationships, including recursive and graph-based
42
+ relationships, for ActiveRecord models in Rails.
43
+ email:
44
+ - shanmugamjanarthan24@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - lib/advanced_relationship_management.rb
50
+ - lib/advanced_relationship_management/advanced_scoping.rb
51
+ - lib/advanced_relationship_management/configuration.rb
52
+ - lib/advanced_relationship_management/counts.rb
53
+ - lib/advanced_relationship_management/cycle_detection.rb
54
+ - lib/advanced_relationship_management/filter_scope.rb
55
+ - lib/advanced_relationship_management/lazy_loading.rb
56
+ - lib/advanced_relationship_management/path_to_root.rb
57
+ - lib/advanced_relationship_management/railtie.rb
58
+ - lib/advanced_relationship_management/recursive_relationships.rb
59
+ - lib/advanced_relationship_management/relationship_visualizations.rb
60
+ - lib/advanced_relationship_management/sibling_relationships.rb
61
+ - lib/advanced_relationship_management/version.rb
62
+ - lib/generators/advanced_relationship_management/install/install_generator.rb
63
+ - lib/generators/advanced_relationship_management/install/templates/create_graph_edges_migration.rb
64
+ - lib/tasks/advanced_relationship_management_tasks.rake
65
+ homepage: https://github.com/janarthanan-shanmugam/advanced_relationship_management
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ allowed_push_host: https://rubygems.org
70
+ homepage_uri: https://github.com/janarthanan-shanmugam/advanced_relationship_management
71
+ source_code_uri: https://github.com/janarthanan-shanmugam/advanced_relationship_management
72
+ changelog_uri: https://github.com/janarthanan-shanmugam/advanced_relationship_management
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 2.6.0
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.4.20
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Advanced Relationship Management for ActiveRecord Models
92
+ test_files: []