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 +7 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +85 -0
- data/Rakefile +12 -0
- data/dagger.png +0 -0
- data/lib/dagraph/edge_model.rb +155 -0
- data/lib/dagraph/has_directed_acyclic_graph.rb +19 -0
- data/lib/dagraph/node_config.rb +24 -0
- data/lib/dagraph/node_model.rb +93 -0
- data/lib/dagraph/version.rb +5 -0
- data/lib/dagraph.rb +15 -0
- data/lib/generators/dagraph/model_generator.rb +53 -0
- data/lib/generators/dagraph/templates/create_edges.rb.erb +14 -0
- metadata +121 -0
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
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
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
|
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: []
|