Skip to content

Commit

Permalink
First version of acts_as_graph_vertex!
Browse files Browse the repository at this point in the history
  • Loading branch information
nathankleyn committed Feb 5, 2015
1 parent 2db6004 commit c3ed1ea
Show file tree
Hide file tree
Showing 10 changed files with 403 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.DS_Store
*.gem
coverage
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
require: rubocop-rspec

LineLength:
Max: 120
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: ruby
rvm:
- 2.2.0
script: bundle exec rspec && bundle exec rubocop
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

gemspec
89 changes: 89 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
PATH
remote: .
specs:
acts_as_graph_vertex (1.0.0)

GEM
remote: https://rubygems.org/
specs:
ast (2.0.0)
astrolabe (1.3.0)
parser (>= 2.2.0.pre.3, < 3.0)
byebug (3.5.1)
columnize (~> 0.8)
debugger-linecache (~> 1.2)
slop (~> 3.6)
coderay (1.1.0)
columnize (0.9.0)
coveralls (0.7.8)
multi_json (~> 1.10)
rest-client (~> 1.7)
simplecov (~> 0.9.1)
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
debugger-linecache (1.2.0)
diff-lcs (1.2.5)
docile (1.1.5)
filewatcher (0.3.6)
trollop (~> 2.0)
method_source (0.8.2)
mime-types (2.4.3)
multi_json (1.10.1)
netrc (0.10.2)
parser (2.2.0.2)
ast (>= 1.1, < 3.0)
powerpack (0.0.9)
pry (0.10.1)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-byebug (2.0.0)
byebug (~> 3.4)
pry (~> 0.10)
rainbow (2.0.0)
rest-client (1.7.2)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rspec (3.1.0)
rspec-core (~> 3.1.0)
rspec-expectations (~> 3.1.0)
rspec-mocks (~> 3.1.0)
rspec-core (3.1.7)
rspec-support (~> 3.1.0)
rspec-expectations (3.1.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.1.0)
rspec-mocks (3.1.3)
rspec-support (~> 3.1.0)
rspec-support (3.1.2)
rubocop (0.28.0)
astrolabe (~> 1.3)
parser (>= 2.2.0.pre.7, < 3.0)
powerpack (~> 0.0.6)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.4)
rubocop-rspec (1.2.1)
ruby-progressbar (1.7.1)
simplecov (0.9.1)
docile (~> 1.1.0)
multi_json (~> 1.0)
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
slop (3.6.0)
term-ansicolor (1.3.0)
tins (~> 1.0)
thor (0.19.1)
tins (1.3.3)
trollop (2.1.1)

PLATFORMS
ruby

DEPENDENCIES
acts_as_graph_vertex!
coveralls (~> 0.7)
filewatcher (~> 0.3)
pry-byebug (~> 2.0)
rspec (~> 3.1)
rubocop (~> 0.28)
rubocop-rspec (~> 1.2)
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# acts_as_graph_vertex [![Build Status](https://travis-ci.org/nathankleyn/acts_as_graph_vertex.svg?branch=master)](https://travis-ci.org/nathankleyn/acts_as_graph_vertex) [![Coverage Status](https://coveralls.io/repos/nathankleyn/acts_as_graph_vertex/badge.png?branch=master)](https://coveralls.io/r/nathankleyn/acts_as_graph_vertex?branch=master)

Simple mixin for adding graph like functions (parents, children, traversal, etc) to any class.

## Installing

You can install this gem via RubyGems:

```sh
gem install acts_as_graph_vertex
```

## Using

Sometimes it's useful to be able to add graph like functionality to existing classes you have. This is where acts_as_graph_vertex comes in! Simply mixin this module, and you'll get basic directed acyclic graph (DAG) functionality for free:

```ruby
require 'acts_as_graph_vertex'

class MyAmazingClass
include ActsAsGraphVertex
end
```

And now you can use the functions:

```ruby
parent = MyAmazingClass.new
child1 = MyAmazingClass.new
child2 = MyAmazingClass.new
child3 = MyAmazingClass.new

# The following calls will create a DAG like the following:
#
# --> child1 --
# / \
# parent-- --> child3
# \ /
# --> child2 --

parent.add_child(child1)
child2.add_parent(child1)
child3.add_parent(child1)
child3.add_parent(child2)
```

You can now traverse the graph from any of these vertices:

```ruby
parent.children # => [child1, child2]
parent.all_children # => [child1, child2, child3]
child3.parents # => [child1, child2]
child3.all_parents # => [child1, child2, parent]
```

Note that this mixin does not currently prevent or handle cyclic dependencies; it's intended to be simple rather than exhaustive. If cycle detection is required, using something as a layer over this mixin such as Tarjan's algorithm for finding strongly connected components of a graph is recommended. Tarjan's algorithm is bundled with the Ruby standard library as `TSort` (despite the naming, this class does not provide topographic sorting).

## License

The MIT License (MIT)

Copyright (c) 2015 Nathan Kleyn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
26 changes: 26 additions & 0 deletions acts_as_graph_vertex.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
$LOAD_PATH.unshift(File.join(__dir__, 'lib'))

Gem::Specification.new do |gem|
gem.name = 'acts_as_graph_vertex'
gem.version = '1.0.0'
gem.homepage = 'https://github.com/nathankleyn/acts_as_graph_vertex'
gem.license = 'MIT'

gem.authors = [
'Nathan Kleyn'
]
gem.email = [
'nathan@nathankleyn.com'
]
gem.summary = 'Simple mixin for adding graph like functions (parents, children, traversal, etc) to any class.'
gem.description = "See #{gem.homepage} for more information!"

gem.files = Dir['**/*'].select { |d| d =~ /^(README.md|lib\/)/ }

gem.add_development_dependency 'coveralls', '~> 0.7'
gem.add_development_dependency 'filewatcher', '~> 0.3'
gem.add_development_dependency 'pry-byebug', '~> 2.0'
gem.add_development_dependency 'rspec', '~> 3.1'
gem.add_development_dependency 'rubocop', '~> 0.28'
gem.add_development_dependency 'rubocop-rspec', '~> 1.2'
end
75 changes: 75 additions & 0 deletions lib/acts_as_graph_vertex.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# A mixin module enabling classes to have parents and children. It provides
# convenience methods for determining dependencies, and depdenants.
#
# Note that in contrast to a tree, this graph module allows multiple parents.
module ActsAsGraphVertex
# The self.included idiom. This is described in great detail in a
# fantastic blog post here:
#
# http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/
#
# Basically, this idiom allows us to add both instance *and* class methods
# to the class that is mixing this module into itself without forcing them
# to call extend and include for this mixin. You'll see this idiom everywhere
# in the Ruby/Rails world, so we use it too.
def self.included(cls)
cls.extend(ClassMethods)
end

# Common methods inherited by all classes
module ClassMethods
attr_writer :parents, :children
end

# Public: The parents linked to this instance.
#
# Returns an Array of Objects.
def parents
@parents ||= []
end

# Public: The children linked to this instance.
#
# Returns an Array of Objects.
def children
@children ||= []
end

# Public: Add a vertex to the parents linked to this instance. Will automatically add as a child of the given parent
# if the given parent also mixes in the graph functionality.
#
# vertex - The Object to add as a parent.
# add_child - Whether to add this item as a child of the given parent.
def add_parent(vertex, add_child = true)
return if parents.include?(vertex)
vertex.add_child(self, false) if add_child && vertex.respond_to?(:add_child)
parents << vertex
end

# Public: Add a vertex to the children linked to this instance. Will automatically add as a parent of the given child
# if the given child also mixes in the graph functionality.
#
# vertex - The Object to add as a child.
# add_parent - Whether to add this item as a parent of the given child.
def add_child(vertex, add_parent = true)
return if children.include?(vertex)
vertex.add_parent(self, false) if add_parent && vertex.respond_to?(:add_parent)
children << vertex
end

# Public: Convenience method to return all of the parents from this vertex in
# the tree upwards to the root of the tree.
#
# Returns an Array of parent Objects.
def all_parents
((parents + parents.map(&:all_parents)).flatten).uniq
end

# Public: Convenience method to return all of the children from this vertex in
# the tree downwards to the leaves of the tree.
#
# Returns an Array of child Objects.
def all_children
(children + children.map(&:all_children).flatten).uniq
end
end
Loading

0 comments on commit c3ed1ea

Please sign in to comment.