edge 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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in edge.gemspec
4
+ gemspec
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/edge/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jack Christensen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,91 @@
1
+ # Edge
2
+
3
+ Edge provides graph functionality to ActiveRecord using recursive common table
4
+ expressions. It has only been tested with PostgreSQL, but it uses Arel for
5
+ SQL generation so it should work with any database and adapter that support
6
+ recursive CTEs.
7
+
8
+ acts_as_forest enables an entire tree or even an entire forest of trees to
9
+ be loaded in a single query. All parent and children associations are
10
+ preloaded.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'edge'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install edge
25
+
26
+ ## Usage
27
+
28
+ acts_as_forest adds tree / multi-tree functionality. All it needs a parent_id
29
+ column. This can be overridden by passing a :foreign_key option to
30
+ acts_as_forest.
31
+
32
+ class Location < ActiveRecord::Base
33
+ acts_as_forest :order => "name"
34
+ end
35
+
36
+ usa = Location.create! :name => "USA"
37
+ illinois = usa.children.create! :name => "Illinois"
38
+ chicago = illinois.children.create! :name => "Chicago"
39
+ indiana = usa.children.create! :name => "Indiana"
40
+ canada = Location.create! :name => "Canada"
41
+ british_columbia = canada.children.create! :name => "British Columbia"
42
+
43
+ Location.root.all # [usa, canada]
44
+ Location.find_forest # [usa, canada] with all children and parents preloaded
45
+ Location.find_tree usa.id # load a single tree.
46
+
47
+ ## Benchmarks
48
+
49
+ Edge includes a performance benchmarks. You can create test forests with a
50
+ configurable number of trees, depth, number of children per node, and
51
+ size of payload per node.
52
+
53
+ jack@moya:~/work/edge$ ruby -I lib -I bench bench/forest_find.rb --help
54
+ Usage: forest_find [options]
55
+ -t, --trees NUM Number of trees to create
56
+ -d, --depth NUM Depth of trees
57
+ -c, --children NUM Number of children per node
58
+ -p, --payload NUM Characters of payload per node
59
+
60
+ Even on slower machines entire trees can be loaded quickly.
61
+
62
+ jack@moya:~/work/edge$ ruby -I lib -I bench bench/forest_find.rb
63
+ Trees: 50
64
+ Depth: 3
65
+ Children per node: 10
66
+ Payload characters per node: 16
67
+ Descendants per tree: 110
68
+ Total records: 5550
69
+ user system total real
70
+ Load entire forest 10 times 4.260000 0.010000 4.270000 ( 4.422442)
71
+ Load one tree 100 times 0.830000 0.040000 0.870000 ( 0.984642)
72
+
73
+ ### Running the benchmarks
74
+
75
+ 1. Create a database such as edge_bench.
76
+ 2. Configure bench/database.yml to connect to it.
77
+ 3. Load bench/database_structure.sql into your bench database.
78
+ 4. Run benchmark scripts from root of gem directory (remember to pass ruby
79
+ the include paths for lib and bench)
80
+
81
+ ## Contributing
82
+
83
+ 1. Fork it
84
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
85
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
86
+ 4. Push to the branch (`git push origin my-new-feature`)
87
+ 5. Create new Pull Request
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,19 @@
1
+ require 'edge'
2
+ require 'benchmark'
3
+ require 'securerandom'
4
+ require 'optparse'
5
+
6
+ database_config = YAML.load_file(File.expand_path("../database.yml", __FILE__))
7
+ ActiveRecord::Base.establish_connection database_config["bench"]
8
+
9
+ class ActsAsForestRecord < ActiveRecord::Base
10
+ acts_as_forest
11
+ end
12
+
13
+ def clean_database
14
+ ActsAsForestRecord.delete_all
15
+ end
16
+
17
+ def vacuum_analyze
18
+ ActiveRecord::Base.connection.execute "VACUUM ANALYZE acts_as_forest_records"
19
+ end
@@ -0,0 +1,4 @@
1
+ bench:
2
+ adapter: postgresql
3
+ encoding: unicode
4
+ database: edge_bench
@@ -0,0 +1,9 @@
1
+ DROP TABLE IF EXISTS acts_as_forest_records;
2
+
3
+ CREATE TABLE acts_as_forest_records(
4
+ id serial PRIMARY KEY,
5
+ parent_id integer REFERENCES acts_as_forest_records,
6
+ payload varchar NOT NULL
7
+ );
8
+
9
+ CREATE INDEX ON acts_as_forest_records (parent_id);
@@ -0,0 +1,71 @@
1
+ require 'benchmark_helper'
2
+
3
+ options = {}
4
+ optparse = OptionParser.new do |opts|
5
+ options[:num_trees] = 50
6
+ opts.on '-t NUM', '--trees NUM', Integer, 'Number of trees to create' do |n|
7
+ options[:num_trees] = n
8
+ end
9
+
10
+ options[:depth] = 3
11
+ opts.on '-d NUM', '--depth NUM', Integer, 'Depth of trees' do |n|
12
+ options[:depth] = n
13
+ end
14
+
15
+ options[:num_children] = 10
16
+ opts.on '-c NUM', '--children NUM', Integer, 'Number of children per node' do |n|
17
+ options[:num_children] = n
18
+ end
19
+
20
+ options[:payload_size] = 16
21
+ opts.on '-p NUM', '--payload NUM', Integer, 'Characters of payload per node' do |n|
22
+ options[:payload_size] = n
23
+ end
24
+ end
25
+
26
+ optparse.parse!
27
+
28
+ NUM_TREES = options[:num_trees]
29
+ DEPTH = options[:depth]
30
+ NUM_CHILDREN = options[:num_children]
31
+ PAYLOAD_SIZE = options[:payload_size]
32
+
33
+
34
+ def create_forest_tree(current_depth = 1, parent = nil)
35
+ node = ActsAsForestRecord.create! :parent => parent, :payload => "z" * PAYLOAD_SIZE
36
+ unless current_depth == DEPTH
37
+ NUM_CHILDREN.times { create_forest_tree current_depth + 1, node }
38
+ end
39
+ node
40
+ end
41
+
42
+ clean_database
43
+ ActsAsForestRecord.transaction do
44
+ NUM_TREES.times { create_forest_tree }
45
+ end
46
+ vacuum_analyze
47
+
48
+ puts "Trees: #{NUM_TREES}"
49
+ puts "Depth: #{DEPTH}"
50
+ puts "Children per node: #{NUM_CHILDREN}"
51
+ puts "Payload characters per node: #{PAYLOAD_SIZE}"
52
+ puts "Descendants per tree: #{ActsAsForestRecord.find_tree(ActsAsForestRecord.root.first.id).descendants.size}"
53
+ puts "Total records: #{ActsAsForestRecord.count}"
54
+
55
+
56
+ Benchmark.bm(40) do |x|
57
+ load_entire_forest_times = 10
58
+ x.report("Load entire forest #{load_entire_forest_times} times") do
59
+ load_entire_forest_times.times do
60
+ ActsAsForestRecord.find_forest
61
+ end
62
+ end
63
+
64
+ load_one_tree_times = 100
65
+ first_tree_id = ActsAsForestRecord.root.first.id
66
+ x.report("Load one tree #{load_one_tree_times} times") do
67
+ load_one_tree_times.times do
68
+ ActsAsForestRecord.find_tree first_tree_id
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/edge/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jack Christensen"]
6
+ gem.email = ["jack@jackchristensen.com"]
7
+ gem.description = %q{Graph functionality for ActiveRecord}
8
+ gem.summary = %q{Graph functionality for ActiveRecord. Provides tree/forest modeling structure that can load entire trees in a single query.}
9
+ gem.homepage = "https://github.com/JackC/edge"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "edge"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Edge::VERSION
17
+
18
+ gem.add_dependency 'activerecord', ">= 3.2.0"
19
+
20
+ gem.add_development_dependency 'pg'
21
+ gem.add_development_dependency 'rspec', "~> 2.8.0"
22
+ gem.add_development_dependency 'guard', ">= 0.10.0"
23
+ gem.add_development_dependency 'guard-rspec', ">= 0.6.0"
24
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_record'
2
+
3
+ require "edge/forest"
4
+ require "edge/version"
5
+
6
+ module Edge
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,146 @@
1
+ module Edge
2
+ module Forest
3
+ # acts_as_forest models a tree/multi-tree structure.
4
+ module ClassMethods
5
+ # options:
6
+ #
7
+ # * foreign_key - column name to use for parent foreign_key (default: parent_id)
8
+ # * order - how to order children (default: none)
9
+ def acts_as_forest(options={})
10
+ options.assert_valid_keys :foreign_key, :order
11
+
12
+ class_attribute :forest_foreign_key
13
+ self.forest_foreign_key = options[:foreign_key] || "parent_id"
14
+
15
+ class_attribute :forest_order
16
+ self.forest_order = options[:order] || nil
17
+
18
+ common_options = {
19
+ :class_name => self,
20
+ :foreign_key => forest_foreign_key
21
+ }
22
+
23
+ belongs_to :parent, common_options
24
+
25
+ children_options = if forest_order
26
+ common_options.merge(:order => forest_order)
27
+ else
28
+ common_options
29
+ end
30
+
31
+ has_many :children, children_options
32
+
33
+ scope :root, where(forest_foreign_key => nil)
34
+
35
+ include Edge::Forest::InstanceMethods
36
+
37
+ # Finds entire forest and preloads all associations. It can be used at
38
+ # the end of an ActiveRecord finder chain.
39
+ #
40
+ # Example:
41
+ # # loads all locations
42
+ # Location.find_forest
43
+ #
44
+ # # loads all nodes with matching names and all there descendants
45
+ # Category.where(:name => %w{clothing books electronics}).find_forest
46
+ def find_forest
47
+ all_nodes = Arel::Table.new(:all_nodes)
48
+
49
+ original_term = (current_scope || scoped).arel
50
+ iterated_term = Arel::SelectManager.new Arel::Table.engine
51
+ iterated_term.from(arel_table)
52
+ .project(arel_table.columns)
53
+ .join(all_nodes)
54
+ .on(arel_table[forest_foreign_key].eq all_nodes[:id])
55
+
56
+ union = original_term.union(iterated_term)
57
+
58
+ as_statement = Arel::Nodes::As.new all_nodes, union
59
+
60
+ manager = Arel::SelectManager.new Arel::Table.engine
61
+ manager.with(:recursive, as_statement).from(all_nodes).project(Arel.star)
62
+ manager.order(forest_order) if forest_order
63
+
64
+ records = find_by_sql manager.to_sql
65
+
66
+ records_by_id = records.each_with_object({}) { |r, h| h[r.id] = r }
67
+
68
+ # Set all children associations to an empty array
69
+ records.each do |r|
70
+ children_association = r.association(:children)
71
+ children_association.target = []
72
+ end
73
+
74
+ top_level_records = []
75
+
76
+ records.each do |r|
77
+ parent = records_by_id[r[forest_foreign_key]]
78
+ if parent
79
+ r.association(:parent).target = parent
80
+ parent.association(:children).target.push(r)
81
+ else
82
+ top_level_records.push(r)
83
+ end
84
+ end
85
+
86
+ top_level_records
87
+ end
88
+
89
+ # Finds an a tree or trees by id.
90
+ #
91
+ # If any requested ids are not found it raises
92
+ # ActiveRecord::RecordNotFound.
93
+ def find_tree(id_or_ids)
94
+ trees = where(:id => id_or_ids).find_forest
95
+ if id_or_ids.kind_of?(Array)
96
+ raise ActiveRecord::RecordNotFound unless trees.size == id_or_ids.size
97
+ trees
98
+ else
99
+ raise ActiveRecord::RecordNotFound if trees.empty?
100
+ trees.first
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ module InstanceMethods
107
+ # Returns the root of this node. If this node is root returns self.
108
+ def root
109
+ parent ? parent.root : self
110
+ end
111
+
112
+ # Returns true is this node is a root or false otherwise
113
+ def root?
114
+ !parent_id
115
+ end
116
+
117
+ # Returns all sibling nodes (nodes that have the same parent). If this
118
+ # node is a root node it returns an empty array.
119
+ def siblings
120
+ parent ? parent.children - [self] : []
121
+ end
122
+
123
+ # Returns all ancestors ordered by nearest ancestors first.
124
+ def ancestors
125
+ _ancestors = []
126
+ node = self
127
+ while(node = node.parent)
128
+ _ancestors.push(node)
129
+ end
130
+
131
+ _ancestors
132
+ end
133
+
134
+ # Returns all descendants
135
+ def descendants
136
+ if children.present?
137
+ children + children.map(&:descendants).flatten
138
+ else
139
+ []
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ ActiveRecord::Base.extend Edge::Forest::ClassMethods
@@ -0,0 +1,3 @@
1
+ module Edge
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ test:
2
+ adapter: postgresql
3
+ encoding: unicode
4
+ database: edge_test
@@ -0,0 +1,7 @@
1
+ DROP TABLE IF EXISTS locations;
2
+
3
+ CREATE TABLE locations(
4
+ id serial PRIMARY KEY,
5
+ parent_id integer REFERENCES locations,
6
+ name varchar NOT NULL
7
+ );
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ class Location < ActiveRecord::Base
4
+ acts_as_forest :order => "name"
5
+ end
6
+
7
+ describe "Edge::Forest" do
8
+ let!(:usa) { Location.create! :name => "USA" }
9
+ let!(:illinois) { Location.create! :parent => usa, :name => "Illinois" }
10
+ let!(:chicago) { Location.create! :parent => illinois, :name => "Chicago" }
11
+ let!(:indiana) { Location.create! :parent => usa, :name => "Indiana" }
12
+ let!(:canada) { Location.create! :name => "Canada" }
13
+ let!(:british_columbia) { Location.create! :parent => canada, :name => "British Columbia" }
14
+
15
+ describe "root?" do
16
+ context "of root node" do
17
+ it "should be true" do
18
+ usa.root?.should == true
19
+ end
20
+ end
21
+
22
+ context "of child node" do
23
+ it "should be false" do
24
+ illinois.root?.should == false
25
+ end
26
+ end
27
+
28
+ context "of leaf node" do
29
+ it "should be root node" do
30
+ chicago.root?.should == false
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "root" do
36
+ context "of root node" do
37
+ it "should be self" do
38
+ usa.root.should == usa
39
+ end
40
+ end
41
+
42
+ context "of child node" do
43
+ it "should be root node" do
44
+ illinois.root.should == usa
45
+ end
46
+ end
47
+
48
+ context "of leaf node" do
49
+ it "should be root node" do
50
+ chicago.root.should == usa
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "parent" do
56
+ context "of root node" do
57
+ it "should be nil" do
58
+ usa.parent.should == nil
59
+ end
60
+ end
61
+
62
+ context "of child node" do
63
+ it "should be parent" do
64
+ illinois.parent.should == usa
65
+ end
66
+ end
67
+
68
+ context "of leaf node" do
69
+ it "should be parent" do
70
+ chicago.parent.should == illinois
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "ancestors" do
76
+ context "of root node" do
77
+ it "should be empty" do
78
+ usa.ancestors.should be_empty
79
+ end
80
+ end
81
+
82
+ context "of leaf node" do
83
+ it "should be ancestors ordered by ascending distance" do
84
+ chicago.ancestors.should == [illinois, usa]
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "siblings" do
90
+ context "of root node" do
91
+ it "should be empty" do
92
+ usa.siblings.should be_empty
93
+ end
94
+ end
95
+
96
+ context "of child node" do
97
+ it "should be other children of parent" do
98
+ illinois.siblings.should include(indiana)
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "children" do
104
+ it "should be children" do
105
+ usa.children.should include(illinois, indiana)
106
+ end
107
+
108
+ it "should be ordered" do
109
+ alabama = Location.create! :parent => usa, :name => "Alabama"
110
+ usa.children.should == [alabama, illinois, indiana]
111
+ end
112
+
113
+ context "of leaf" do
114
+ it "should be empty" do
115
+ chicago.children.should be_empty
116
+ end
117
+ end
118
+ end
119
+
120
+ describe "descendants" do
121
+ it "should be all descendants" do
122
+ usa.descendants.should include(illinois, indiana, chicago)
123
+ end
124
+
125
+ context "of leaf" do
126
+ it "should be empty" do
127
+ chicago.descendants.should be_empty
128
+ end
129
+ end
130
+ end
131
+
132
+ describe "root scope" do
133
+ it "returns only root nodes" do
134
+ Location.root.all.should include(usa, canada)
135
+ end
136
+ end
137
+
138
+ describe "find_forest" do
139
+ it "preloads all parents and children" do
140
+ forest = Location.find_forest
141
+
142
+ Location.with_scope(
143
+ :find => Location.where("purposely fail if any Location find happens here")
144
+ ) do
145
+ forest.each do |tree|
146
+ tree.descendants.each do |node|
147
+ node.parent.should be
148
+ node.children.should be_kind_of(Array)
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ it "works when scoped" do
155
+ forest = Location.where(:name => "USA").find_forest
156
+ forest.should include(usa)
157
+ end
158
+
159
+ it "preloads children in proper order" do
160
+ alabama = Location.create! :parent => usa, :name => "Alabama"
161
+ forest = Location.find_forest
162
+ tree = forest.find { |l| l.id == usa.id }
163
+ tree.children.should == [alabama, illinois, indiana]
164
+ end
165
+ end
166
+
167
+ describe "find_tree" do
168
+ it "finds by id" do
169
+ tree = Location.find_tree usa.id
170
+ tree.should == usa
171
+ end
172
+
173
+ it "finds multiple trees by id" do
174
+ trees = Location.find_tree [indiana.id, illinois.id]
175
+ trees.should include(indiana, illinois)
176
+ end
177
+
178
+ it "raises ActiveRecord::RecordNotFound when id is not found" do
179
+ expect{Location.find_tree -1}.to raise_error(ActiveRecord::RecordNotFound)
180
+ end
181
+
182
+ it "raises ActiveRecord::RecordNotFound when not all ids are not found" do
183
+ expect{Location.find_tree [indiana.id, -1]}.to raise_error(ActiveRecord::RecordNotFound)
184
+ end
185
+
186
+ end
187
+
188
+ end
@@ -0,0 +1,41 @@
1
+ require 'edge'
2
+ require 'yaml'
3
+
4
+ require 'rspec'
5
+
6
+ database_config = YAML.load_file(File.expand_path("../database.yml", __FILE__))
7
+ ActiveRecord::Base.establish_connection database_config["test"]
8
+
9
+
10
+
11
+ # class HstoreRecord < ActiveRecord::Base
12
+ # serialize :properties, Surus::Hstore::Serializer.new
13
+ # end
14
+
15
+ # class TextArrayRecord < ActiveRecord::Base
16
+ # serialize :texts, Surus::Array::TextSerializer.new
17
+ # end
18
+
19
+ # class IntegerArrayRecord < ActiveRecord::Base
20
+ # serialize :integers, Surus::Array::IntegerSerializer.new
21
+ # end
22
+
23
+ # class FloatArrayRecord < ActiveRecord::Base
24
+ # serialize :floats, Surus::Array::FloatSerializer.new
25
+ # end
26
+
27
+ # class DecimalArrayRecord < ActiveRecord::Base
28
+ # serialize :decimals, Surus::Array::DecimalSerializer.new
29
+ # end
30
+
31
+
32
+
33
+ RSpec.configure do |config|
34
+ config.around :disable_transactions => nil do |example|
35
+ ActiveRecord::Base.transaction do
36
+ example.call
37
+ raise ActiveRecord::Rollback
38
+ end
39
+ end
40
+ end
41
+
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: edge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jack Christensen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &14555680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *14555680
25
+ - !ruby/object:Gem::Dependency
26
+ name: pg
27
+ requirement: &14555260 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *14555260
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &14554720 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.8.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *14554720
47
+ - !ruby/object:Gem::Dependency
48
+ name: guard
49
+ requirement: &14554220 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.10.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *14554220
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard-rspec
60
+ requirement: &14553760 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 0.6.0
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *14553760
69
+ description: Graph functionality for ActiveRecord
70
+ email:
71
+ - jack@jackchristensen.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - Gemfile
79
+ - Guardfile
80
+ - LICENSE
81
+ - README.md
82
+ - Rakefile
83
+ - bench/benchmark_helper.rb
84
+ - bench/database.yml
85
+ - bench/database_structure.sql
86
+ - bench/forest_find.rb
87
+ - edge.gemspec
88
+ - lib/edge.rb
89
+ - lib/edge/forest.rb
90
+ - lib/edge/version.rb
91
+ - spec/database.yml
92
+ - spec/database_structure.sql
93
+ - spec/forest_spec.rb
94
+ - spec/spec_helper.rb
95
+ homepage: https://github.com/JackC/edge
96
+ licenses: []
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 1.8.17
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: Graph functionality for ActiveRecord. Provides tree/forest modeling structure
119
+ that can load entire trees in a single query.
120
+ test_files: []