acts_as_graph_diagram 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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +178 -0
  4. data/Rakefile +5 -0
  5. data/bin/test +7 -0
  6. data/lib/acts_as_graph_diagram/edge_scopes.rb +23 -0
  7. data/lib/acts_as_graph_diagram/node/graph_calculator.rb +129 -0
  8. data/lib/acts_as_graph_diagram/node.rb +120 -0
  9. data/lib/acts_as_graph_diagram/railtie.rb +11 -0
  10. data/lib/acts_as_graph_diagram/version.rb +5 -0
  11. data/lib/acts_as_graph_diagram.rb +12 -0
  12. data/lib/generators/USAGE +5 -0
  13. data/lib/generators/acts_as_graph_diagram_generator.rb +30 -0
  14. data/lib/generators/templates/migration.rb +21 -0
  15. data/lib/generators/templates/model.rb +24 -0
  16. data/test/acts_as_graph_diagram_test.rb +44 -0
  17. data/test/dummy/Rakefile +39 -0
  18. data/test/dummy/app/assets/config/manifest.js +3 -0
  19. data/test/dummy/app/assets/images/.keep +0 -0
  20. data/test/dummy/app/assets/stylesheets/application.css +1 -0
  21. data/test/dummy/app/channels/application_cable/channel.rb +6 -0
  22. data/test/dummy/app/channels/application_cable/connection.rb +6 -0
  23. data/test/dummy/app/controllers/application_controller.rb +4 -0
  24. data/test/dummy/app/controllers/concerns/.keep +0 -0
  25. data/test/dummy/app/controllers/gods_controller.rb +67 -0
  26. data/test/dummy/app/helpers/application_helper.rb +4 -0
  27. data/test/dummy/app/helpers/gods_helper.rb +4 -0
  28. data/test/dummy/app/javascript/application.js +94 -0
  29. data/test/dummy/app/jobs/application_job.rb +9 -0
  30. data/test/dummy/app/mailers/application_mailer.rb +6 -0
  31. data/test/dummy/app/models/application_record.rb +5 -0
  32. data/test/dummy/app/models/concerns/.keep +0 -0
  33. data/test/dummy/app/models/edge.rb +24 -0
  34. data/test/dummy/app/models/god.rb +14 -0
  35. data/test/dummy/app/views/gods/_form.html.erb +22 -0
  36. data/test/dummy/app/views/gods/_god.html.erb +16 -0
  37. data/test/dummy/app/views/gods/edit.html.erb +6 -0
  38. data/test/dummy/app/views/gods/index.html.erb +35 -0
  39. data/test/dummy/app/views/gods/new.html.erb +5 -0
  40. data/test/dummy/app/views/gods/show.html.erb +9 -0
  41. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  42. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  43. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  44. data/test/dummy/bin/importmap +5 -0
  45. data/test/dummy/bin/rails +6 -0
  46. data/test/dummy/bin/rake +6 -0
  47. data/test/dummy/bin/setup +35 -0
  48. data/test/dummy/config/application.rb +23 -0
  49. data/test/dummy/config/boot.rb +7 -0
  50. data/test/dummy/config/cable.yml +10 -0
  51. data/test/dummy/config/database.yml +25 -0
  52. data/test/dummy/config/environment.rb +7 -0
  53. data/test/dummy/config/environments/development.rb +71 -0
  54. data/test/dummy/config/environments/production.rb +89 -0
  55. data/test/dummy/config/environments/test.rb +62 -0
  56. data/test/dummy/config/importmap.rb +43 -0
  57. data/test/dummy/config/initializers/content_security_policy.rb +26 -0
  58. data/test/dummy/config/initializers/filter_parameter_logging.rb +10 -0
  59. data/test/dummy/config/initializers/inflections.rb +17 -0
  60. data/test/dummy/config/initializers/permissions_policy.rb +12 -0
  61. data/test/dummy/config/locales/en.yml +33 -0
  62. data/test/dummy/config/puma.rb +45 -0
  63. data/test/dummy/config/routes.rb +11 -0
  64. data/test/dummy/config/storage.yml +34 -0
  65. data/test/dummy/config.ru +8 -0
  66. data/test/dummy/db/migrate/20220606102242_create_gods.rb +11 -0
  67. data/test/dummy/db/migrate/20220612090334_acts_as_graph_diagram_migration.rb +21 -0
  68. data/test/dummy/db/schema.rb +35 -0
  69. data/test/dummy/db/seeds.rb +53 -0
  70. data/test/dummy/lib/assets/.keep +0 -0
  71. data/test/dummy/log/.keep +0 -0
  72. data/test/dummy/public/404.html +67 -0
  73. data/test/dummy/public/422.html +67 -0
  74. data/test/dummy/public/500.html +66 -0
  75. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  76. data/test/dummy/public/apple-touch-icon.png +0 -0
  77. data/test/dummy/public/favicon.ico +0 -0
  78. data/test/dummy/test/controllers/gods_controller_test.rb +50 -0
  79. data/test/dummy/test/system/gods_test.rb +43 -0
  80. data/test/fixtures/edges.yml +111 -0
  81. data/test/fixtures/gods.yml +81 -0
  82. data/test/test_helper.rb +18 -0
  83. metadata +434 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bcef649a07aef82c036b7c6df58bf00513702a32c5960dc3af5d4088267cc618
4
+ data.tar.gz: fc4a5712c0d0b05f165dc665a324a612cf496ee04eafc69ef587cb1253ee9ecb
5
+ SHA512:
6
+ metadata.gz: 1cb85f7ee1c208c5571fa3d447045028917f92d91bf3b70deead1afa01b06a43f5c795b2b9bf324e3b111879ec966fb9b9ff3bced0806b37a9c3b4a65d7a0d11
7
+ data.tar.gz: c27c720b88ca5ae95f4a4c721f5e625625a49b3ae035e0c140a369cb416aeaf0f72a2411521301203d45a4ecb2f600c5ab7a85697226120dc7774b0e7b7f02aa
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 smapira
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # Acts As Graph Diagram
2
+
3
+ Acts As Graph Diagram extends Active Record to add simple function for draw the Force Directed Graph with html.
4
+
5
+ ![Ruby](https://img.shields.io/badge/Ruby-CC342D?style=for-the-badge&logo=ruby&logoColor=white)
6
+ [![Gem Version](https://badge.fury.io/rb/acts_as_graph_diagram.svg)](https://badge.fury.io/rb/acts_as_graph_diagram)
7
+ ![](https://ruby-gem-downloads-badge.herokuapp.com/acts_as_graph_diagram)
8
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop-hq/rubocop)
9
+ [![CircleCI](https://circleci.com/gh/routeflags/acts_as_graph_diagram.svg?style=svg)](https://circleci.com/gh/routeflags/acts_as_graph_diagram)
10
+
11
+ ## See It Work
12
+
13
+ ![acts_as_graph_diagram](https://user-images.githubusercontent.com/25024587/173231019-ede998c4-333a-48dd-b2da-96f04e1fce86.gif)
14
+
15
+ ## Usage
16
+
17
+ Append the line to your model file like below:
18
+ ```ruby
19
+ class God < ApplicationRecord
20
+ acts_as_graph_diagram
21
+ end
22
+
23
+ God.find_by(name: 'Rheā').add_destination God.find_by(name: 'Hēra', cost: 1)
24
+ # => #<Edge:0x000000010b0d4560
25
+ # id: 1,
26
+ # comment: "",
27
+ # cost: 0,
28
+ # directed: true,
29
+ # destination_type: "God",
30
+ # destination_id: 2,
31
+ # departure_type: "God",
32
+ # departure_id: 1,
33
+ # created_at: Sun, 12 Jun 2022 11:11:06.995007000 UTC +00:00,
34
+ # updated_at: Sun, 12 Jun 2022 11:11:06.995007000 UTC +00:00>
35
+
36
+ God.find_by(name: 'Rheā').connecting_count
37
+ # => 1
38
+
39
+ God.find_by(name: 'Rheā').destinations
40
+ # => [#<Edge:0x000000010b5642b0
41
+ # id: 1,
42
+ # comment: "",
43
+ # cost: 0,
44
+ # directed: true,
45
+ # destination_type: "God",
46
+ # destination_id: 2,
47
+ # departure_type: "God",
48
+ # departure_id: 1,
49
+ # created_at: Sun, 12 Jun 2022 11:11:06.995007000 UTC +00:00,
50
+ # updated_at: Sun, 12 Jun 2022 11:11:06.995007000 UTC +00:00>]
51
+
52
+ God.find_by(name: 'Rheā').aheads.first.destination
53
+ # => #<God:0x000000010b5efb58 id: 2, name: "Hēra", created_at: Sun, 12 Jun 2022 11:11:06.984341000 UTC +00:00, updated_at: Sun, 12 Jun 2022 11:11:06.984341000 UTC +00:00>
54
+ ```
55
+
56
+ ### Methods
57
+
58
+ * aheads
59
+ * behinds
60
+ * add_destination(node, comment: '', cost: 0)
61
+ * add_departure(node, comment: '', cost: 0)
62
+ * get_destination(node)
63
+ * get_departure(node)
64
+ * remove_destination(node)
65
+ * remove_departure(node)
66
+ * connecting?(node)
67
+ * connecting_count()
68
+ * add_connection(node, directed: false, comment: '', cost: 0)
69
+ * sum_cost()
70
+ * sum_tree_cost()
71
+ * assemble_tree_nodes()
72
+
73
+ ### Draws the graph diagram with D3.js
74
+
75
+ 1. Append the lines to your controller file like below:
76
+ ```ruby
77
+ class GodsController < ApplicationController
78
+ def data_network
79
+ render json: { 'nodes' => God.all.pluck(:id, :name)
80
+ .map { |x| Hash[id: x[0], name: x[1]] },
81
+ 'links' => Edge.all.pluck(:destination_id, :departure_id)
82
+ .map { |x| Hash[target: x[0], source: x[1]] } }
83
+ end
84
+ end
85
+ ```
86
+
87
+ 2. And append the line to your routes.rb file like below:
88
+ ```ruby
89
+ Rails.application.routes.draw do
90
+ get 'data_network' => 'gods#data_network'
91
+ end
92
+ ```
93
+
94
+ 3. Then append the line to your javascript file like below:
95
+ ```javascript
96
+ // v7.4.4
97
+ d3.json("http://127.0.0.1:3000/data_network").then(function (graph) {});
98
+ ```
99
+
100
+ ### Calculates the Program Evaluation and Review Technique (PERT)
101
+
102
+ ![Pert_chart_colored](https://user-images.githubusercontent.com/25024587/174105277-213a955a-b783-43ae-be98-1174d9256273.gif)
103
+
104
+ > [PERT Chart. Drawn in Adobe Illustrator - inspired by a chart at netmba.com. Created by Jeremy Kemp. 2005/01/11 From Wikipedia, the free encyclopedia](https://en.wikipedia.org/wiki/Program_evaluation_and_review_technique)
105
+
106
+ ```ruby
107
+ Milestone.create(name: 10)
108
+ Milestone.create(name: 20)
109
+ Milestone.create(name: 30)
110
+ Milestone.create(name: 40)
111
+ Milestone.create(name: 50)
112
+
113
+ Milestone.find_by(name: 10).add_destination(Milestone.find_by(name: 20), cost: 3)
114
+ Milestone.find_by(name: 10).add_destination(Milestone.find_by(name: 30), cost: 4)
115
+ Milestone.find_by(name: 30).add_destination(Milestone.find_by(name: 40), cost: 1)
116
+ Milestone.find_by(name: 40).add_destination(Milestone.find_by(name: 50), cost: 3)
117
+ Milestone.find_by(name: 30).add_destination(Milestone.find_by(name: 50), cost: 2)
118
+ Milestone.find_by(name: 20).add_destination(Milestone.find_by(name: 50), cost: 3)
119
+
120
+ Milestone.find_by(name: 10).sum_tree_cost
121
+
122
+ # => 16
123
+ ```
124
+
125
+ ## Installation
126
+ Add this line to your application's Gemfile:
127
+
128
+ ```ruby
129
+ gem "acts_as_graph_diagram"
130
+ ```
131
+
132
+ And then execute:
133
+ ```bash
134
+ $ bundle
135
+ $ bin/rails generate acts_as_graph_diagram
136
+ $ bin/rails db:migrate
137
+ ```
138
+
139
+ Or install it yourself as:
140
+ ```bash
141
+ $ gem install acts_as_graph_diagram
142
+ ```
143
+
144
+ ## Development
145
+ ### Rails console
146
+ ```bash
147
+ test/dummy/bin/rails console
148
+ ```
149
+
150
+ ### Test
151
+ ```bash
152
+ bin/test
153
+ ```
154
+
155
+ ## Contributing
156
+ Bug reports and pull requests are welcome on Github at https://github.com/routeflags/acts_as_graph_diagram. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
157
+
158
+ ## License
159
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
160
+
161
+ ## Changelog
162
+ available [here](https://github.com/routeflags/acts_as_graph_diagram/main/CHANGELOG.md).
163
+
164
+ ## Code of Conduct
165
+ Everyone interacting in the ActsAsTreeDiagram project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/routeflags/acts_as_graph_diagram/main/CODE_OF_CONDUCT.md).
166
+
167
+ ## You may enjoy owning other libraries and my company.
168
+
169
+ * [acts_as_graph_diagram: ActsAsTreeDiagram extends ActsAsTree to add simple function for draw tree diagram with html.](https://github.com/routeflags/acts_as_graph_diagram)
170
+ * [timeline_rails_helper: The TimelineRailsHelper provides a timeline_molecules_tag helper to draw a vertical time line usable with vanilla CSS.](https://github.com/routeflags/timeline_rails_helper)
171
+ * [株式会社旗指物](https://blog.routeflags.com/)
172
+
173
+ ## Аcknowledgments
174
+
175
+ - [activerecord - Model an undirected graph in Rails? - Stack Overflow](https://stackoverflow.com/questions/7976301/model-an-undirected-graph-in-rails)
176
+ - [tcocca/acts_as_follower: A Gem to add Follow functionality for models](https://github.com/tcocca/acts_as_follower)
177
+ - [Force layout | D3 in Depth](https://www.d3indepth.com/force-layout/)
178
+ - [Rubyを使って「なぜ関数プログラミングは重要か」を読み解く(改定)─ 前編 ─ 但し後編の予定なし](https://melborne.github.io/2013/01/21/why-fp-with-ruby/)
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ require 'bundler/gem_tasks'
data/bin/test ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH << File.expand_path('../test', __dir__)
5
+
6
+ require 'bundler/setup'
7
+ require 'rails/plugin/test'
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsGraphDiagram # :nodoc:
4
+ ##
5
+ # This module represents a act of edge.
6
+ module EdgeScopes
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ # returns Edge records where destination is the record passed in.
11
+ # @param [Node] node
12
+ scope :select_destinations, ->(node) { where(destination_id: node.id, destination_type: node.class.name) }
13
+
14
+ # returns Edge records where departure is the record passed in.
15
+ # @param [Node] node
16
+ scope :select_departures, ->(node) { where(departure_id: node.id, departure_type: node.class.name) }
17
+
18
+ # returns Edge records where departure or destination are the record passed in.
19
+ # @param [Node] node
20
+ scope :select_connections, ->(node) { select_destinations(node).or(select_departures(node)) }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsGraphDiagram # :nodoc:
4
+ ##
5
+ # This module represents a array of node.
6
+ class Nodes < Array
7
+ # @param [Proc] functional
8
+ # @param [Proc] meta
9
+ # @param [Array] values
10
+ # @param [Symbol] operator
11
+ # @return Proc
12
+ def read_tree(functional, meta, values, operator)
13
+ return values if !defined?(empty?) || empty?
14
+
15
+ meta[first.destination.read_tree(functional, meta, values, operator),
16
+ ActsAsGraphDiagram::Nodes.new(tail)
17
+ .read_tree(functional, meta, values, operator)]
18
+ end
19
+
20
+ # @param [Proc] functional
21
+ # @param [Nodes] values
22
+ # @return Proc
23
+ def linear(functional, values)
24
+ return self if empty? || !first.attributes.include?(:destination)
25
+
26
+ functional[first.destination,
27
+ ActsAsGraphDiagram::Nodes.new(tail)
28
+ .linear(functional, values)]
29
+ end
30
+
31
+ # @return Array
32
+ def tail
33
+ drop 1
34
+ end
35
+
36
+ # @return Proc
37
+ def confluence
38
+ # @param [Proc] functional
39
+ # @param [Nodes] values
40
+ lambda do |x, list = self|
41
+ ActsAsGraphDiagram::Nodes.new([x] + list)
42
+ end
43
+ end
44
+
45
+ # @return Proc
46
+ def append
47
+ # @param [Proc] functional
48
+ # @param [Nodes] values
49
+ lambda do |nodes = self, list|
50
+ nodes.linear confluence,
51
+ ActsAsGraphDiagram::Nodes.new(list)
52
+ end
53
+ end
54
+ end
55
+
56
+ module Node # :nodoc:
57
+ ##
58
+ # This module represents a calculation of graph.
59
+ module GraphCalculator
60
+ extend ActiveSupport::Concern
61
+
62
+ included do
63
+ # @param [Proc] functional
64
+ # @param [Proc] meta
65
+ # @param [Any] value
66
+ # @param [Symbol] operator
67
+ # @return Proc
68
+ def read_tree(functional, meta, value, operator = :self)
69
+ argument = if operator == :self
70
+ self
71
+ else
72
+ public_send(operator)
73
+ end
74
+ functional[argument,
75
+ ActsAsGraphDiagram::Nodes
76
+ .new(aheads.where.not(destination_id: id).to_a)
77
+ .read_tree(functional, meta, value, operator)]
78
+ end
79
+
80
+ # @return Integer
81
+ def sum_tree_cost
82
+ read_tree addition, addition, 0, :sum_cost
83
+ end
84
+
85
+ # @return Proc
86
+ def addition
87
+ # @param [Any] x
88
+ # @param [Any] y
89
+ ->(x, y) { x + y }
90
+ end
91
+
92
+ def sum_cost
93
+ aheads.sum(:cost)
94
+ end
95
+
96
+ # @param [Proc] functional
97
+ # @param [Proc] meta
98
+ # @return Proc
99
+ def linear(functional, meta)
100
+ raise NotImplementedError
101
+ ->(x, y) { functional[meta[x], y] }
102
+ end
103
+
104
+ # @return Proc
105
+ def append
106
+ # @param [Nodes] nodes
107
+ # @param [Array] values
108
+ lambda do |nodes = self, values|
109
+ nodes.linear confluence, ActsAsGraphDiagram::Nodes.new(values)
110
+ end
111
+ end
112
+
113
+ # @return Proc
114
+ def confluence
115
+ # @param [Node] node
116
+ # @param [Nodes] nodes
117
+ lambda do |node, nodes = self|
118
+ ActsAsGraphDiagram::Nodes.new([node] + nodes)
119
+ end
120
+ end
121
+
122
+ # @return [Node]
123
+ def assemble_tree_nodes
124
+ read_tree confluence, append, []
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'acts_as_graph_diagram/node/graph_calculator'
4
+
5
+ module ActsAsGraphDiagram # :nodoc:
6
+ module Node
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods # :nodoc:
12
+ def acts_as_node
13
+ has_many :behinds, as: :destination,
14
+ class_name: 'Edge',
15
+ dependent: :destroy
16
+ has_many :aheads, as: :departure,
17
+ class_name: 'Edge',
18
+ dependent: :destroy
19
+ include ActsAsGraphDiagram::Node::InstanceMethods
20
+ include ActsAsGraphDiagram::Node::GraphCalculator
21
+ end
22
+ end
23
+
24
+ module InstanceMethods # :nodoc:
25
+ # rubocop:disable Style/HashSyntax
26
+
27
+ # Creates a new destination record for this instance to connect the passed object.
28
+ # @param [Node] node
29
+ # @param [String] comment
30
+ # @param [Integer] cost
31
+ # @return [Edge]
32
+ def add_destination(node, comment: '', cost: 0)
33
+ aheads.select_destinations(node)
34
+ .where(comment: comment, cost: cost)
35
+ .first_or_create!
36
+ end
37
+
38
+ # Creates a new departure record for this instance to connect the passed object.
39
+ # @param [Node] node
40
+ # @param [String] comment
41
+ # @param [Integer] cost
42
+ # @return [Edge]
43
+ def add_departure(node, comment: '', cost: 0)
44
+ behinds.select_departures(node)
45
+ .where(comment: comment, cost: cost)
46
+ .first_or_create!
47
+ end
48
+
49
+ # Creates a new undirected connection record for this instance to connect the passed object.
50
+ # @param [Node] node
51
+ # @param [Boolean] directed
52
+ # @param [String] comment
53
+ # @param [Integer] cost
54
+ # @return [Edge]
55
+ def add_connection(node, directed: false, comment: '', cost: 0)
56
+ Edge.where(destination: node,
57
+ directed: directed,
58
+ departure: self,
59
+ comment: comment,
60
+ cost: cost).first_or_create!
61
+ end
62
+ # rubocop:enable Style/HashSyntax
63
+
64
+ # Returns a destination node record for the current instance.
65
+ # @param [Node] node
66
+ # @return [Edge]
67
+ def get_destination(node)
68
+ aheads.select_destinations(node).first
69
+ end
70
+
71
+ # Deletes the destination record if it exists.
72
+ # @param [Node] node
73
+ # @return [Edge|nil]
74
+ def remove_destination(node)
75
+ get_destination(node).try(:destroy)
76
+ end
77
+
78
+ # Returns a departure node record for the current instance.
79
+ # @param [Node] node
80
+ # @return [Edge]
81
+ def get_departure(node)
82
+ behinds.select_departures(node).first
83
+ end
84
+
85
+ # Deletes the destination record if it exists.
86
+ # @param [Node] node
87
+ # @return [Edge|nil]
88
+ def remove_departure(node)
89
+ get_departure(node).try(:destroy)
90
+ end
91
+
92
+ # Returns a undirected node record for the current instance.
93
+ # @param [Node] node
94
+ # @return [Edge]
95
+ def get_connection(node)
96
+ Edge.select_connections(node).first
97
+ end
98
+
99
+ # Deletes the undirected record if it exists.
100
+ # @param [Node] node
101
+ # @return [Edge|nil]
102
+ def remove_connection(node)
103
+ get_connection(node).try(:destroy)
104
+ end
105
+
106
+ # Returns true if this instance is connecting the object passed as an argument.
107
+ # @param [Node] node
108
+ # @return [Boolean]
109
+ def connecting?(node)
110
+ node.connecting_count.positive?
111
+ end
112
+
113
+ # Returns the number of objects this instance is following.
114
+ # @return [Integer]
115
+ def connecting_count
116
+ Edge.select_connections(self).count
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsGraphDiagram # :nodoc:
4
+ class Railtie < ::Rails::Railtie # :nodoc:
5
+ initializer 'acts_as_graph_diagram.active_record' do |_app|
6
+ ActiveSupport.on_load :active_record do
7
+ include ActsAsGraphDiagram::Node
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsGraphDiagram
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'acts_as_graph_diagram/version'
4
+ require 'acts_as_graph_diagram/railtie'
5
+
6
+ # Specify this extension if you want to model a graph
7
+ # network structure by providing any edge association.
8
+ module ActsAsGraphDiagram
9
+ autoload :Node, 'acts_as_graph_diagram/node'
10
+ autoload :EdgeScopes, 'acts_as_graph_diagram/edge_scopes.rb'
11
+ require 'acts_as_graph_diagram/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
12
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+ rails generate acts_as_graph_diagram
3
+
4
+ no need to specify a name after acts_as_graph_diagram as you can not change the model name from Follow
5
+ the acts_as_graph_diagram_migration file will be created in db/migrate
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/migration'
5
+
6
+ class ActsAsGraphDiagramGenerator < Rails::Generators::Base # :nodoc:
7
+ include Rails::Generators::Migration
8
+
9
+ def self.source_root
10
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
11
+ end
12
+
13
+ # Implement the required interface for Rails::Generators::Migration.
14
+ # taken from https://github.com/rails/rails/blob/master/activerecord/lib/rails/generators/active_record.rb
15
+ def self.next_migration_number(dirname)
16
+ if ActiveRecord::Base.timestamped_migrations
17
+ Time.now.utc.strftime('%Y%m%d%H%M%S')
18
+ else
19
+ format('%.3d', (current_migration_number(dirname) + 1))
20
+ end
21
+ end
22
+
23
+ def create_migration_file
24
+ migration_template 'migration.rb', 'db/migrate/acts_as_graph_diagram_migration.rb'
25
+ end
26
+
27
+ def create_model
28
+ template 'model.rb', File.join('app/models', 'edge.rb')
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActsAsGraphDiagramMigration < ActiveRecord::Migration[4.2] # :nodoc:
4
+ def self.up
5
+ create_table :edges, force: true do |t|
6
+ t.string :comment, default: ''
7
+ t.integer :cost, default: 0
8
+ t.boolean :directed, default: true
9
+ t.references :destination, polymorphic: true, null: true
10
+ t.references :departure, polymorphic: true, null: true
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :edges, %w[departure_id departure_type], name: 'fk_edges_departure'
15
+ add_index :edges, %w[destination_id destination_type], name: 'fk_edges_destination'
16
+ end
17
+
18
+ def self.down
19
+ drop_table :edges
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: edges
6
+ #
7
+ # id :integer not null, primary key
8
+ # comment :string default("")
9
+ # cost :integer default(0)
10
+ # directed :boolean default(TRUE)
11
+ # destination_type :string
12
+ # destination_id :integer
13
+ # departure_type :string
14
+ # departure_id :integer
15
+ # created_at :datetime
16
+ # updated_at :datetime
17
+ #
18
+ class Edge < ActiveRecord::Base
19
+ extend ActsAsGraphDiagram::Node
20
+ include ActsAsGraphDiagram::EdgeScopes
21
+
22
+ belongs_to :destination, polymorphic: true, optional: true
23
+ belongs_to :departure, polymorphic: true, optional: true
24
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class ActsAsGraphDiagramTest < ActiveSupport::TestCase
6
+ test 'it has a version number' do
7
+ assert ActsAsGraphDiagram::VERSION
8
+ end
9
+
10
+ test 'be defined' do
11
+ assert God.first.respond_to?(:aheads)
12
+ assert God.first.respond_to?(:behinds)
13
+ assert God.first.respond_to?(:add_destination)
14
+ assert God.first.respond_to?(:add_departure)
15
+ assert God.first.respond_to?(:get_destination)
16
+ assert God.first.respond_to?(:get_departure)
17
+ assert God.first.respond_to?(:remove_destination)
18
+ assert God.first.respond_to?(:remove_departure)
19
+ assert God.first.respond_to?(:connecting?)
20
+ assert God.first.respond_to?(:connecting_count)
21
+ assert God.first.respond_to?(:add_connection)
22
+ assert God.first.respond_to?(:sum_cost)
23
+ assert God.first.respond_to?(:sum_tree_cost)
24
+ assert God.first.respond_to?(:assemble_tree_nodes)
25
+ end
26
+
27
+ test 'calculate sum_cost' do
28
+ God.find(3).add_destination(God.find(5), cost: 4)
29
+ assert_equal God.find(3).sum_cost, 4
30
+ end
31
+
32
+ test 'calculate sum_tree_cost' do
33
+ God.find(4).add_destination(God.find(6), cost: 4)
34
+ God.find(6).add_destination(God.find(7), cost: 3)
35
+ assert_equal God.find(4).sum_tree_cost, 7
36
+ end
37
+
38
+ test 'call assemble_tree_nodes' do
39
+ God.find(4).add_destination(God.find(6), cost: 4)
40
+ God.find(6).add_destination(God.find(7), cost: 3)
41
+ God.find(6).add_destination(God.find(7), cost: 3)
42
+ assert_equal God.find(4).assemble_tree_nodes.size, 3
43
+ end
44
+ end