advanced_relationship_management 0.1.0

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