dagraph 1.0.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: 55db1fe8fe777780fc058b9e709f1574f9f264f68fae09861bba8e4d90a9ea83
4
+ data.tar.gz: bc31712c6329ad2b96ab7643b49676bb34a2ca3e9cee59bee683d24228baafe7
5
+ SHA512:
6
+ metadata.gz: a8410f16caec145acdd86980699494fb3c5843b1030a84da99d8f9092d6428f974df2543f31a342e3fae697011cfbff43752e36a3909bd28faf971dc5aff3ffe
7
+ data.tar.gz: 9da67d338b071d33f651f88fd34b491c83c5dbc0bddfa9a224c82006d7d04b3dfbe0a5a569da191981cb3368b2c298f8d64c85354e564ba821f68a5eabe32e50
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-05-31
4
+
5
+ - Initial release
6
+
7
+ ## [1.0.0] - 2025-05-12
8
+
9
+ - First stable release
10
+ - Handle children and parents
11
+ - Improve generator
12
+ - Add support for multiple graphs in the same model
13
+ - Handle weights for each edges
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in dagraph.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
11
+
12
+ gem "appraisal"
13
+ gem "database_cleaner"
14
+ gem "sqlite3"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Guillaume Dott
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Dagraph
2
+
3
+ Dagraph is a gem which allows you to represent DAG hierarchy using your ActiveRecord models.
4
+ With a directed acyclic graph, you can represent hierarchical data where children may have multiple parents.
5
+
6
+ ## Installation
7
+
8
+ Install the gem and add to the application's Gemfile by executing:
9
+
10
+ $ bundle add dagraph
11
+
12
+ To add a DAG to a model, you can use the generator:
13
+
14
+ ```
15
+ bin/rails generate dagraph:model {MODEL_NAME}
16
+ ```
17
+
18
+ It will add the required line to your model, create the file for the edges model and the required migration.
19
+
20
+ ## Usage
21
+
22
+ After setting up a DAG in one of your model, the following methods are available to use the DAG:
23
+
24
+ | Method name | Description |
25
+ | ----------- | ----------- |
26
+ | Model.roots | Get all objects without a parent |
27
+ | Model.leaves | Get all objects without a child |
28
+ | Model#parents | Get all parents (direct or not) attached to your object |
29
+ | Model#children | Get all children (direct or not) attached to your object |
30
+ | Model#parent\_edges | Get all parent edges attached to your object, this method return instances from your edge class |
31
+ | Model#child\_edges | Get all children edges attached to your object, this method return instances from your edge class |
32
+ | Model#direct\_parents | Get all direct parents attached to your object |
33
+ | Model#direct\_children | Get all direct children attached to your object |
34
+ | Model#direct\_parent\_edges | Get all direct parent edges attached to your object, this method return instances from your edge class |
35
+ | Model#direct\_child\_edges | Get all direct children edges attached to your object, this method return instances from your edge class |
36
+ | Model#parent\_of?(node) | Check if your object is a parent of the node |
37
+ | Model#child\_of?(node) | Check if your object is a child of the node |
38
+ | Model#direct\_parent\_of?(node) | Check if your object is a direct parent of the node |
39
+ | Model#direct\_child\_of?(node) | Check if your object is a direct child of the node |
40
+ | Model#childdren\_at\_depth(depth) | Get all children of your object at a specific depth |
41
+ | Model#root? | Check if your object is at the root of your tree (it has no parent) |
42
+ | Model#child? | Check if your object is not at the root of your tree (it has one or more parents) |
43
+ | Model#leaf? | Check if your object is a leaf in your tree (it has no child) |
44
+
45
+ You can add parents or children to your objects by using the `direct_parents` and `direct_children` associations:
46
+ ```ruby
47
+ # Define all parents for your model
48
+ model.direct_parents = [node]
49
+
50
+ # Add one parent to your model
51
+ model.direct_parents << node
52
+
53
+ # Remove one parent for your model
54
+ model.direct_parents.destroy(node)
55
+
56
+ # Define all children for your model
57
+ model.direct_children = [node]
58
+
59
+ # Add one parent to your model
60
+ model.direct_children << node
61
+
62
+ # Remove one child for your model
63
+ model.direct_children.destroy(node)
64
+
65
+ ```
66
+
67
+ ## Development
68
+
69
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
70
+
71
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
72
+
73
+ ## Useful links about DAG
74
+
75
+ https://www.codeproject.com/Articles/22824/A-Model-to-Represent-Directed-Acyclic-Graphs-DAG-o#Figure2
76
+ https://arxiv.org/pdf/2211.11159
77
+ https://www.baeldung.com/cs/dag-applications
78
+
79
+ ## Contributing
80
+
81
+ Bug reports and pull requests are welcome on GitHub at https://github.com/gdott9/dagraph.
82
+
83
+ ## License
84
+
85
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ task default: :test
data/dagger.png ADDED
Binary file
@@ -0,0 +1,155 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/array/access'
3
+
4
+ module Dagraph
5
+ module EdgeModel
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ belongs_to :entry_edge, class_name: model_name.to_s, optional: true
10
+ belongs_to :direct_edge, class_name: model_name.to_s, optional: true
11
+ belongs_to :exit_edge, class_name: model_name.to_s, optional: true
12
+
13
+ belongs_to :parent, class_name: _dagraph.nodes_class_name
14
+ belongs_to :child, class_name: _dagraph.nodes_class_name
15
+
16
+ scope :direct, -> { where hops: 0 }
17
+ scope :implicit, -> { where.not hops: 0 }
18
+
19
+ attr_readonly :entry_edge_id, :direct_edge_id, :exit_edge_id,
20
+ :parent_id, :child_id, :hops, :source
21
+
22
+ after_create :add_implicit_edges!
23
+ after_save :calculate_implicit_edges_weight!
24
+
25
+ before_destroy :check_readonly
26
+ before_destroy :destroy_implicit_edges!
27
+ after_destroy :calculate_direct_edges_weight!
28
+
29
+ validate :_dagraph_cycle_detection
30
+ end
31
+
32
+ def direct?
33
+ hops.zero?
34
+ end
35
+
36
+ def implicit?
37
+ !direct?
38
+ end
39
+
40
+ def readonly?
41
+ implicit?
42
+ end
43
+
44
+ def dependent_implicit_edges
45
+ edges_list = self.class.where(direct_edge: self).pluck(:id)
46
+ loop do
47
+ new_edges_list = self.class.where.not(id: edges_list)
48
+ .and(
49
+ self.class.where(entry_edge_id: edges_list).or(self.class.where(exit_edge_id: edges_list))
50
+ )
51
+ edges_list += new_edges_list
52
+ break if new_edges_list.empty?
53
+ end
54
+
55
+ self.class.where(id: edges_list.excluding(id))
56
+ end
57
+
58
+ private
59
+
60
+ def check_readonly
61
+ _raise_readonly_record_error if readonly?
62
+ end
63
+
64
+ def direct_edges
65
+ @direct_edges ||= self.class.direct.where(child: child, source: source)
66
+ end
67
+
68
+ def add_implicit_edges!
69
+ return unless direct?
70
+
71
+ self.class.with_advisory_lock("dagraph_#{self.class.table_name}") do
72
+ self.class.where(id: id).update_all(entry_edge_id: id, direct_edge_id: id, exit_edge_id: id)
73
+
74
+ direct_edges_count = direct_edges.count
75
+ if direct_edges_count > 1
76
+ update_column :weight, weight / direct_edges_count
77
+ direct_edges.where.not(id: self).each do |edge|
78
+ edge.update! weight: edge.weight * (direct_edges_count - 1) / direct_edges_count
79
+ end
80
+ end
81
+
82
+ source_condition = source ? "= ?" : "IS NULL"
83
+ select = self.class.sanitize_sql_array(
84
+ [
85
+ "x.id, :id, :id, x.parent_id, :child_id, x.hops + 1, x.weight * :weight / 100, x.source",
86
+ id: id, child_id: child_id, weight: weight
87
+ ]
88
+ )
89
+ where = self.class.sanitize_sql_for_conditions(
90
+ ["x.child_id = ? AND x.source #{source_condition}", parent_id, source].compact
91
+ )
92
+ self.class.connection.exec_insert insert_query(select, where)
93
+
94
+ select = self.class.sanitize_sql_array(
95
+ [
96
+ ":id, :id, x.id, :parent_id, x.child_id, x.hops + 1, x.weight * :weight / 100, x.source",
97
+ id: id, parent_id: parent_id, weight: weight
98
+ ]
99
+ )
100
+ where = self.class.sanitize_sql_for_conditions(
101
+ ["x.parent_id = ? AND x.source #{source_condition}", child_id, source].compact
102
+ )
103
+ self.class.connection.exec_insert insert_query(select, where)
104
+
105
+ select = self.class.sanitize_sql_array(
106
+ [
107
+ "x.id, :id, y.id, x.parent_id, y.child_id, x.hops + y.hops + 2, x.weight * y.weight * :weight / 10000, x.source",
108
+ id: id, weight: weight
109
+ ]
110
+ )
111
+ where = self.class.sanitize_sql_for_conditions(
112
+ ["x.child_id = ? AND y.parent_id = ? AND x.source #{source_condition} AND y.source #{source_condition}", parent_id, child_id, source, source].compact
113
+ )
114
+ self.class.connection.exec_insert insert_query(select, where, cross_join: true)
115
+ end
116
+ end
117
+
118
+ def insert_query(select, where, cross_join: false)
119
+ <<-SQL.squish
120
+ INSERT INTO #{self.class.quoted_table_name}
121
+ (entry_edge_id, direct_edge_id, exit_edge_id, parent_id, child_id, hops, weight, source)
122
+ SELECT #{select}
123
+ FROM #{self.class.quoted_table_name} x
124
+ #{cross_join ? "CROSS JOIN #{self.class.quoted_table_name} y" : ''}
125
+ WHERE #{where}
126
+ SQL
127
+ end
128
+
129
+ def destroy_implicit_edges!
130
+ return unless direct?
131
+
132
+ dependent_implicit_edges.delete_all
133
+ end
134
+
135
+ def calculate_implicit_edges_weight!
136
+ return unless direct? && weight_previously_changed?
137
+
138
+ dependent_implicit_edges.update_all(
139
+ self.class.sanitize_sql_array(["weight = weight * ? / ?", weight, weight_previously_was])
140
+ )
141
+ end
142
+
143
+ def calculate_direct_edges_weight!
144
+ direct_edges.each do |edge|
145
+ edge.update! weight: edge.weight + weight / direct_edges.size
146
+ end
147
+ end
148
+
149
+ def _dagraph_cycle_detection
150
+ if parent == child || child.parent_of?(parent)
151
+ errors.add :base, :cycle_detected, message: 'You cannot add a parent as a child'
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,19 @@
1
+ module Dagraph
2
+ module HasDirectedAcyclicGraph
3
+ def has_directed_acyclic_graph(options = {})
4
+ class_attribute :_dagraph
5
+ self._dagraph = Dagraph::NodeConfig.new(self, **options)
6
+ include Dagraph::NodeModel
7
+
8
+ _dagraph.edges_class.is_directed_acyclic_graph_edge _dagraph
9
+ end
10
+ alias_method :has_dag, :has_directed_acyclic_graph
11
+
12
+ def is_directed_acyclic_graph_edge(node_config)
13
+ class_attribute :_dagraph
14
+ self._dagraph = node_config
15
+
16
+ include Dagraph::EdgeModel
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ module Dagraph
2
+ class NodeConfig
3
+ def initialize(model_class, **options)
4
+ @model_class = model_class
5
+ @options = options
6
+ end
7
+
8
+ def nodes_class_name
9
+ @model_class.to_s
10
+ end
11
+
12
+ def nodes_class
13
+ @model_class
14
+ end
15
+
16
+ def edges_class_name
17
+ "#{@model_class}Edge"
18
+ end
19
+
20
+ def edges_class
21
+ edges_class_name.constantize
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,93 @@
1
+ require 'active_support/concern'
2
+
3
+ module Dagraph
4
+ module NodeModel
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :roots, -> { where.not id: _dagraph.edges_class.select(:child_id) }
9
+ scope :leaves, -> { where.not id: _dagraph.edges_class.select(:parent_id) }
10
+
11
+ has_many :parent_edges, class_name: _dagraph.edges_class_name, foreign_key: :child_id
12
+ has_many :parents, -> { distinct }, class_name: model_name.to_s, through: :parent_edges
13
+ has_many :child_edges, class_name: _dagraph.edges_class_name, foreign_key: :parent_id
14
+ has_many :children, -> { distinct }, class_name: model_name.to_s, through: :child_edges
15
+
16
+ has_many :roots, -> { distinct.roots }, class_name: model_name.to_s, through: :parent_edges, source: :parent
17
+ has_many :leaves, -> { distinct.leaves }, class_name: model_name.to_s, through: :child_edges, source: :child
18
+
19
+ has_many :direct_parent_edges, -> { direct }, class_name: _dagraph.edges_class_name, foreign_key: :child_id, inverse_of: :child, dependent: :destroy
20
+ has_many :direct_parents, -> { distinct }, class_name: model_name.to_s, through: :direct_parent_edges, source: :parent, dependent: :destroy
21
+ has_many :direct_child_edges, -> { direct }, class_name: _dagraph.edges_class_name, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
22
+ has_many :direct_children, -> { distinct }, class_name: model_name.to_s, through: :direct_child_edges, source: :child, dependent: :destroy
23
+ end
24
+
25
+ def parents_to_hash(depth: nil)
26
+ graph_to_hash parents, :direct_children, depth: depth
27
+ end
28
+
29
+ def children_to_hash(depth: nil)
30
+ graph_to_hash children, :direct_parents, depth: depth
31
+ end
32
+
33
+ def children_at_depth(depth)
34
+ self.class.where id: child_edges.where(hops: depth - 1).select(:child_id)
35
+ end
36
+
37
+ def self_and_children
38
+ self.class.where(id: self).or(self.class.where(id: children))
39
+ end
40
+
41
+ def parent_of?(node)
42
+ return false if node.nil?
43
+ children.include?(node)
44
+ end
45
+
46
+ def child_of?(node)
47
+ return false if node.nil?
48
+ node.parent_of?(self)
49
+ end
50
+
51
+ def direct_parent_of?(node)
52
+ return false if node.nil?
53
+ direct_children.include?(node)
54
+ end
55
+
56
+ def direct_child_of?(node)
57
+ return false if node.nil?
58
+ node.direct_parent_of?(self)
59
+ end
60
+
61
+ def root?
62
+ parent_edges.empty?
63
+ end
64
+
65
+ def child?
66
+ !root?
67
+ end
68
+
69
+ def leaf?
70
+ child_edges.empty?
71
+ end
72
+
73
+ private
74
+
75
+ def graph_to_hash(nodes, association, depth: nil)
76
+ hash = {self => {}}
77
+ ids = {self => hash[self]}
78
+
79
+ nodes = nodes.where(self.class._dagraph.edges_class.table_name => {hops: ..depth - 1}) if depth.present? && depth > 0
80
+
81
+ nodes.includes(association).each do |node|
82
+ ids[node] = {} unless ids.key?(node)
83
+
84
+ node.send(association).each do |parent_node|
85
+ ids[parent_node] = {} unless ids.key?(parent_node)
86
+ ids[parent_node][node] = ids[node]
87
+ end
88
+ end
89
+
90
+ hash
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dagraph
4
+ VERSION = "1.0.0"
5
+ end
data/lib/dagraph.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dagraph/version"
4
+
5
+ require 'active_support'
6
+ require 'with_advisory_lock'
7
+
8
+ require_relative "dagraph/has_directed_acyclic_graph"
9
+ require_relative "dagraph/node_config"
10
+ require_relative "dagraph/node_model"
11
+ require_relative "dagraph/edge_model"
12
+
13
+ ActiveSupport.on_load :active_record do
14
+ ActiveRecord::Base.send :extend, Dagraph::HasDirectedAcyclicGraph
15
+ end
@@ -0,0 +1,53 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module Dagraph
4
+ module Generators # :nodoc:
5
+ class ModelGenerator < Rails::Generators::NamedBase # :nodoc:
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ def self.next_migration_number(number)
11
+ ActiveRecord::Generators::Base.next_migration_number(number)
12
+ end
13
+
14
+ def add_dag_line_to_node_model
15
+ inject_into_class "app/models/#{klass.model_name.singular}.rb", klass.to_s do
16
+ " has_directed_acyclic_graph\n"
17
+ end
18
+ end
19
+ def create_edge_model_file
20
+ create_file "app/models/#{dagraph.edges_class_name.underscore}.rb", <<~RUBY
21
+ class #{dagraph.edges_class_name} < ApplicationRecord
22
+ end
23
+ RUBY
24
+ end
25
+
26
+ def create_migration_file
27
+ migration_template 'create_edges.rb.erb', "db/migrate/create_#{migration_name}.rb"
28
+ end
29
+
30
+ private
31
+
32
+ def migration_name
33
+ dagraph.edges_class_name.underscore.pluralize
34
+ end
35
+
36
+ def migration_class_name
37
+ "Create#{migration_name.camelize}"
38
+ end
39
+
40
+ def migration_version
41
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
42
+ end
43
+
44
+ def klass
45
+ @klass ||= class_name.camelize.constantize
46
+ end
47
+
48
+ def dagraph
49
+ @dagraph ||= Dagraph::NodeConfig.new(klass)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,14 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :<%= migration_name %>, force: true do |t|
4
+ t.references :entry_edge, null: true, foreign_key: {to_table: :<%= migration_name %>}
5
+ t.references :direct_edge, null: true, foreign_key: {to_table: :<%= migration_name %>}
6
+ t.references :exit_edge, null: true, foreign_key: {to_table: :<%= migration_name %>}
7
+ t.references :parent, null: false, foreign_key: {to_table: :<%= klass.table_name %>}
8
+ t.references :child, null: false, foreign_key: {to_table: :<%= klass.table_name %>}
9
+ t.integer :hops, index: true, null: false, default: 0
10
+ t.float :weight, null: false, default: 100
11
+ t.string :source, index: true, null: false, default: 'default'
12
+ end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dagraph
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Guillaume Dott
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 9.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 9.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 6.0.0
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: 9.0.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 6.0.0
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: 9.0.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: with_advisory_lock
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 4.0.0
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: 6.0.0
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 4.0.0
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: 6.0.0
73
+ description: |
74
+ Dagraph is a gem which allows you to represent DAG hierarchy using your ActiveRecord models.
75
+ With a directed acyclic graph, you can represent hierarchical data where children may have multiple parents.
76
+ email:
77
+ - guillaume+github@dott.fr
78
+ executables: []
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - CHANGELOG.md
83
+ - Gemfile
84
+ - LICENSE.txt
85
+ - README.md
86
+ - Rakefile
87
+ - dagger.png
88
+ - lib/dagraph.rb
89
+ - lib/dagraph/edge_model.rb
90
+ - lib/dagraph/has_directed_acyclic_graph.rb
91
+ - lib/dagraph/node_config.rb
92
+ - lib/dagraph/node_model.rb
93
+ - lib/dagraph/version.rb
94
+ - lib/generators/dagraph/model_generator.rb
95
+ - lib/generators/dagraph/templates/create_edges.rb.erb
96
+ homepage: https://github.com/gdott9/dagraph
97
+ licenses:
98
+ - MIT
99
+ metadata:
100
+ homepage_uri: https://github.com/gdott9/dagraph
101
+ changelog_uri: https://github.com/gdott9/dagraph/blob/main/CHANGELOG.md
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 2.6.0
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 3.5.22
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Add support for directed acyclic graphs (DAG) to your ActiveRecord model
121
+ test_files: []