edge 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []