closure_tree 4.2.4 → 4.2.5
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 +6 -14
- data/README.md +7 -4
- data/lib/closure_tree/finders.rb +27 -13
- data/lib/closure_tree/hierarchy_maintenance.rb +27 -18
- data/lib/closure_tree/support.rb +1 -3
- data/lib/closure_tree/version.rb +1 -1
- data/spec/db/schema.rb +3 -3
- data/spec/parallel_spec.rb +84 -9
- data/spec/spec_helper.rb +5 -1
- data/spec/support/models.rb +1 -1
- data/spec/tag_examples.rb +8 -0
- metadata +27 -27
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
NzE2ZTUxMDY0YzAxMDY1ZTFjMzlkM2ZjZDcwNDJhMjg3YWRiODU2NWYxMjgz
|
10
|
-
YjJkNzIxNzA2MjQwYTliMjEyNTVmYjZkMDExNGZlMzM3MjVjYjY0NGZjZDNi
|
11
|
-
ODA4MzQ4OTFkYmE1YWYzODQ4ZDY2YmRhMjIyZWJhZDQ4NjU1MDA=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ZThkMTI0YTU0OTJlNjEzMjdlMjU2Mjc5M2I5NGI2M2UxODQwMjc0NDUzMTEw
|
14
|
-
NWZmNDM1Yjk3YmI4M2I5OGIzYWQ2Zjg0YzE0NWIxYjMyNTY3ZjBjOTdmZDBh
|
15
|
-
OTYyMGEzZjFjYTAyZjBlZWY3NDMyNDNhMDllODY2ZjNhZWVkYjk=
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e95e5a8b184eb1ac818d7fbe79f9fd332cc94fe1
|
4
|
+
data.tar.gz: 1663703503122d4a0a2fe17d6270ab19c5a40596
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 27afdd7889a43d53c25298cf35fb754ef41fc7c3728ad07e9a048cc33a7932638cabae18951e1fdbe089060bb97b2d7496e6d4368b6bc79bfac80f1b832d86d5
|
7
|
+
data.tar.gz: 42bd0e1731527939884764a16279ca0511ad18bd701ba620a70fe17cf7abe6321c466ba297333e1311884c0b142a091c14fe9ba10e87efc5f083b669ee1229a0
|
data/README.md
CHANGED
@@ -8,6 +8,7 @@ and tracking user referrals.
|
|
8
8
|
[](http://travis-ci.org/mceachen/closure_tree)
|
9
9
|
[](http://rubygems.org/gems/closure_tree)
|
10
10
|
[](https://codeclimate.com/github/mceachen/closure_tree)
|
11
|
+
[](https://gemnasium.com/mceachen/closure_tree)
|
11
12
|
|
12
13
|
Substantially more efficient than
|
13
14
|
[ancestry](https://github.com/stefankroes/ancestry) and
|
@@ -26,7 +27,7 @@ closure_tree has some great features:
|
|
26
27
|
* 3 SQL INSERT/UPDATEs on node reparenting
|
27
28
|
* __Support for Rails 3.0, 3.1, 3.2, and 4.0__
|
28
29
|
* Support for reparenting children (and all their progeny)
|
29
|
-
* Support for [concurrency](#concurrency) (using [with_advisory_lock](https://github/mceachen/with_advisory_lock))
|
30
|
+
* Support for [concurrency](#concurrency) (using [with_advisory_lock](https://github.com/mceachen/with_advisory_lock))
|
30
31
|
* Support for polymorphism [STI](#sti) within the hierarchy
|
31
32
|
* ```find_or_create_by_path``` for [building out hierarchies quickly and conveniently](#find_or_create_by_path)
|
32
33
|
* Support for [deterministic ordering](#deterministic-ordering) of children
|
@@ -87,11 +88,13 @@ Note that closure_tree only supports Rails 3.0 and later, and has test coverage
|
|
87
88
|
t.integer :generations, :null => false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example.
|
88
89
|
end
|
89
90
|
|
90
|
-
# For "all progeny of…" selects:
|
91
|
-
add_index :tag_hierarchies, [:ancestor_id, :descendant_id
|
91
|
+
# For "all progeny of…" and leaf selects:
|
92
|
+
add_index :tag_hierarchies, [:ancestor_id, :descendant_id, :generations],
|
93
|
+
:unique => true, :name => "tag_anc_desc_udx"
|
92
94
|
|
93
|
-
# For "all ancestors of…" selects
|
95
|
+
# For "all ancestors of…" selects,
|
94
96
|
add_index :tag_hierarchies, [:descendant_id]
|
97
|
+
:name => "tag_desc_idx"
|
95
98
|
end
|
96
99
|
end
|
97
100
|
```
|
data/lib/closure_tree/finders.rb
CHANGED
@@ -11,17 +11,21 @@ module ClosureTree
|
|
11
11
|
# Find a child node whose +ancestry_path+ minus self.ancestry_path is +path+
|
12
12
|
def find_or_create_by_path(path, attributes = {}, find_before_lock = true)
|
13
13
|
attributes[:type] ||= self.type if _ct.subclass? && _ct.has_type?
|
14
|
+
# only bother trying to find_by_path on the first call:
|
14
15
|
(find_before_lock && find_by_path(path, attributes)) || begin
|
16
|
+
subpath = path.is_a?(Enumerable) ? path.dup : [path]
|
17
|
+
return self if subpath.empty?
|
18
|
+
child_name = subpath.shift
|
19
|
+
attrs = attributes.merge(_ct.name_sym => child_name)
|
15
20
|
_ct.with_advisory_lock do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
self.class.new(attrs).tap { |ea| self.children << ea }
|
21
|
+
# shenanigans because children.create is bound to the superclass
|
22
|
+
# (in the case of polymorphism):
|
23
|
+
child = self.children.where(attrs).first || begin
|
24
|
+
self.class.new(attrs).tap do |ea|
|
25
|
+
# We know that there isn't a cycle, because we just created it, and
|
26
|
+
# cycle detection is expensive when the node is deep.
|
27
|
+
ea._ct_skip_cycle_detection!
|
28
|
+
self.children << ea
|
25
29
|
end
|
26
30
|
end
|
27
31
|
child.find_or_create_by_path(subpath, attributes, false)
|
@@ -48,6 +52,11 @@ module ClosureTree
|
|
48
52
|
|
49
53
|
module ClassMethods
|
50
54
|
|
55
|
+
# Fix deprecation warning:
|
56
|
+
def _ct_all
|
57
|
+
(ActiveRecord::VERSION::MAJOR >= 4 ) ? all : scoped
|
58
|
+
end
|
59
|
+
|
51
60
|
def without(instance)
|
52
61
|
if instance.new_record?
|
53
62
|
all
|
@@ -79,7 +88,7 @@ module ClosureTree
|
|
79
88
|
|
80
89
|
def with_ancestor(*ancestors)
|
81
90
|
ancestor_ids = ancestors.map { |ea| ea.is_a?(ActiveRecord::Base) ? ea._ct_id : ea }
|
82
|
-
scope = ancestor_ids.blank? ?
|
91
|
+
scope = ancestor_ids.blank? ? _ct_all : joins(:ancestor_hierarchies).
|
83
92
|
where("#{_ct.hierarchy_table_name}.ancestor_id" => ancestor_ids).
|
84
93
|
where("#{_ct.hierarchy_table_name}.generations > 0").
|
85
94
|
readonly(false)
|
@@ -118,7 +127,8 @@ module ClosureTree
|
|
118
127
|
scope = where(_ct.name_sym => path.pop).readonly(false)
|
119
128
|
scope = ct_scoped_attributes(scope, attributes)
|
120
129
|
last_joined_table = _ct.table_name
|
121
|
-
|
130
|
+
# MySQL doesn't support more than 61 joined tables (!!):
|
131
|
+
path.first(50).reverse.each_with_index do |ea, idx|
|
122
132
|
next_joined_table = "p#{idx}"
|
123
133
|
scope = scope.joins(<<-SQL)
|
124
134
|
INNER JOIN #{_ct.quoted_table_name} AS #{next_joined_table}
|
@@ -129,8 +139,12 @@ module ClosureTree
|
|
129
139
|
scope = ct_scoped_attributes(scope, attributes, next_joined_table)
|
130
140
|
last_joined_table = next_joined_table
|
131
141
|
end
|
132
|
-
|
133
|
-
|
142
|
+
result = scope.where("#{last_joined_table}.#{_ct.parent_column_name}" => parent_id).first
|
143
|
+
if path.size > 50 && result
|
144
|
+
find_by_path(path[50..-1], attributes, result.primary_key)
|
145
|
+
else
|
146
|
+
result
|
147
|
+
end
|
134
148
|
end
|
135
149
|
|
136
150
|
# Find or create nodes such that the +ancestry_path+ is +path+
|
@@ -11,8 +11,13 @@ module ClosureTree
|
|
11
11
|
before_destroy :_ct_before_destroy
|
12
12
|
end
|
13
13
|
|
14
|
+
def _ct_skip_cycle_detection!
|
15
|
+
@_ct_skip_cycle_detection = true
|
16
|
+
end
|
17
|
+
|
14
18
|
def _ct_validate
|
15
|
-
if
|
19
|
+
if !@_ct_skip_cycle_detection &&
|
20
|
+
changes[_ct.parent_column_name] &&
|
16
21
|
parent.present? &&
|
17
22
|
parent.self_and_ancestors.include?(self)
|
18
23
|
errors.add(_ct.parent_column_sym, "You cannot add an ancestor as a descendant")
|
@@ -31,9 +36,11 @@ module ClosureTree
|
|
31
36
|
end
|
32
37
|
|
33
38
|
def _ct_before_destroy
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
_ct.with_advisory_lock do
|
40
|
+
delete_hierarchy_references
|
41
|
+
if _ct.options[:dependent] == :nullify
|
42
|
+
self.class.find(self.id).children.each { |c| c.rebuild! }
|
43
|
+
end
|
37
44
|
end
|
38
45
|
true # don't prevent destruction
|
39
46
|
end
|
@@ -57,20 +64,22 @@ module ClosureTree
|
|
57
64
|
end
|
58
65
|
|
59
66
|
def delete_hierarchy_references
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
FROM
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
67
|
+
_ct.with_advisory_lock do
|
68
|
+
# The crazy double-wrapped sub-subselect works around MySQL's limitation of subselects on the same table that is being mutated.
|
69
|
+
# It shouldn't affect performance of postgresql.
|
70
|
+
# See http://dev.mysql.com/doc/refman/5.0/en/subquery-errors.html
|
71
|
+
# Also: PostgreSQL doesn't support INNER JOIN on DELETE, so we can't use that.
|
72
|
+
_ct.connection.execute <<-SQL
|
73
|
+
DELETE FROM #{_ct.quoted_hierarchy_table_name}
|
74
|
+
WHERE descendant_id IN (
|
75
|
+
SELECT DISTINCT descendant_id
|
76
|
+
FROM (SELECT descendant_id
|
77
|
+
FROM #{_ct.quoted_hierarchy_table_name}
|
78
|
+
WHERE ancestor_id = #{_ct.quote(id)}
|
79
|
+
) AS x )
|
80
|
+
OR descendant_id = #{_ct.quote(id)}
|
81
|
+
SQL
|
82
|
+
end
|
74
83
|
end
|
75
84
|
|
76
85
|
module ClassMethods
|
data/lib/closure_tree/support.rb
CHANGED
data/lib/closure_tree/version.rb
CHANGED
data/spec/db/schema.rb
CHANGED
@@ -46,7 +46,7 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
46
46
|
t.string "name"
|
47
47
|
end
|
48
48
|
|
49
|
-
force_add_index "tag_hierarchies", [:ancestor_id, :descendant_id], :unique => true, :name => "tag_anc_desc_idx"
|
49
|
+
force_add_index "tag_hierarchies", [:ancestor_id, :descendant_id, :generations], :unique => true, :name => "tag_anc_desc_idx"
|
50
50
|
force_add_index "tag_hierarchies", [:descendant_id], :name => "tag_desc_idx"
|
51
51
|
|
52
52
|
create_table "users", :force => true do |t|
|
@@ -66,7 +66,7 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
66
66
|
t.integer "generations", :null => false
|
67
67
|
end
|
68
68
|
|
69
|
-
force_add_index "referral_hierarchies", [:ancestor_id, :descendant_id], :unique => true, :name => "ref_anc_desc_idx"
|
69
|
+
force_add_index "referral_hierarchies", [:ancestor_id, :descendant_id, :generations], :unique => true, :name => "ref_anc_desc_idx"
|
70
70
|
force_add_index "referral_hierarchies", [:descendant_id], :name => "ref_desc_idx"
|
71
71
|
|
72
72
|
create_table "labels", :force => true do |t|
|
@@ -82,7 +82,7 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
82
82
|
t.integer "generations", :null => false
|
83
83
|
end
|
84
84
|
|
85
|
-
force_add_index "label_hierarchies", [:ancestor_id, :descendant_id], :unique => true, :name => "lh_anc_desc_idx"
|
85
|
+
force_add_index "label_hierarchies", [:ancestor_id, :descendant_id, :generations], :unique => true, :name => "lh_anc_desc_idx"
|
86
86
|
force_add_index "label_hierarchies", [:descendant_id], :name => "lh_desc_idx"
|
87
87
|
|
88
88
|
create_table "cuisine_types", :force => true do |t|
|
data/spec/parallel_spec.rb
CHANGED
@@ -3,29 +3,33 @@ require 'spec_helper'
|
|
3
3
|
parallelism_is_broken = begin
|
4
4
|
# Rails < 3.2 has known bugs with parallelism
|
5
5
|
(ActiveRecord::VERSION::MAJOR <= 3 && ActiveRecord::VERSION::MINOR < 2) ||
|
6
|
-
|
7
|
-
|
6
|
+
# SQLite doesn't support parallel writes
|
7
|
+
ENV["DB"] =~ /sqlite/
|
8
8
|
end
|
9
9
|
|
10
10
|
describe "threadhot" do
|
11
11
|
|
12
12
|
before :each do
|
13
|
+
ActiveRecord::Base.connection.reconnect!
|
13
14
|
TagHierarchy.delete_all
|
14
15
|
Tag.delete_all
|
15
|
-
@iterations = 5
|
16
|
-
@workers = 6 # Travis CI workers can't reliably handle larger numbers
|
17
16
|
@parent = nil
|
17
|
+
# These values seem to allow Travis to reliably pass:
|
18
|
+
@iterations = 5
|
19
|
+
@workers = 6
|
20
|
+
@time_between_runs = 3
|
18
21
|
end
|
19
22
|
|
20
23
|
def find_or_create_at_even_second(run_at)
|
21
24
|
sleep(run_at - Time.now.to_f)
|
22
25
|
ActiveRecord::Base.connection.reconnect!
|
23
|
-
(@parent || Tag).find_or_create_by_path([run_at.to_s, :a, :b, :c]
|
26
|
+
(@parent || Tag).find_or_create_by_path([run_at.to_s, :a, :b, :c])
|
24
27
|
end
|
25
28
|
|
26
29
|
def run_workers
|
27
|
-
|
28
|
-
|
30
|
+
expected_thread_setup_time = 4
|
31
|
+
start_time = Time.now.to_i + expected_thread_setup_time
|
32
|
+
@times = @iterations.times.collect { |ea| start_time + (ea * @time_between_runs) }
|
29
33
|
@names = @times.collect { |ea| ea.to_s }
|
30
34
|
@threads = @workers.times.collect do
|
31
35
|
Thread.new do
|
@@ -35,7 +39,6 @@ describe "threadhot" do
|
|
35
39
|
@threads.each { |ea| ea.join }
|
36
40
|
end
|
37
41
|
|
38
|
-
|
39
42
|
it "class method will not create dupes" do
|
40
43
|
run_workers
|
41
44
|
Tag.roots.collect { |ea| ea.name.to_i }.should =~ @times
|
@@ -57,9 +60,81 @@ describe "threadhot" do
|
|
57
60
|
|
58
61
|
it "creates dupe roots without advisory locks" do
|
59
62
|
# disable with_advisory_lock:
|
60
|
-
Tag.
|
63
|
+
Tag.stub(:with_advisory_lock).and_return { |lock_name, &block| block.call }
|
61
64
|
run_workers
|
62
65
|
Tag.where(:name => @names).size.should > @iterations
|
63
66
|
end
|
64
67
|
|
68
|
+
it "fails to deadlock from parallel sibling churn" do
|
69
|
+
# target should be non-trivially long to maximize time spent in hierarchy maintenance
|
70
|
+
target = Tag.find_or_create_by_path(('a'..'z').to_a + ('A'..'Z').to_a)
|
71
|
+
expected_children = (1..100).to_a.map { |ea| "root ##{ea}" }
|
72
|
+
children_to_add = expected_children.dup
|
73
|
+
added_children = []
|
74
|
+
children_to_delete = []
|
75
|
+
deleted_children = []
|
76
|
+
creator_threads = @workers.times.map do
|
77
|
+
Thread.new do
|
78
|
+
ActiveRecord::Base.connection.reconnect!
|
79
|
+
begin
|
80
|
+
name = children_to_add.shift
|
81
|
+
unless name.nil?
|
82
|
+
target.find_or_create_by_path(name)
|
83
|
+
children_to_delete << name
|
84
|
+
added_children << name
|
85
|
+
end
|
86
|
+
end while !children_to_add.empty?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
run_destruction = true
|
90
|
+
destroyer_threads = @workers.times.map do
|
91
|
+
Thread.new do
|
92
|
+
ActiveRecord::Base.connection.reconnect!
|
93
|
+
begin
|
94
|
+
victim = children_to_delete.shift
|
95
|
+
if victim
|
96
|
+
target.children.where(:name => victim).first.destroy
|
97
|
+
deleted_children << victim
|
98
|
+
else
|
99
|
+
sleep rand # wait for moar victims
|
100
|
+
end
|
101
|
+
end while run_destruction || !children_to_delete.empty?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
creator_threads.each { |ea| ea.join }
|
105
|
+
run_destruction = false
|
106
|
+
destroyer_threads.each { |ea| ea.join }
|
107
|
+
|
108
|
+
added_children.should =~ expected_children
|
109
|
+
deleted_children.should =~ expected_children
|
110
|
+
end
|
111
|
+
|
112
|
+
# Oh, yeah, I'm totes monkey patching in a bad shuffle. I AM A NAUGHTY MONKAY
|
113
|
+
class Array
|
114
|
+
def bad_shuffle!(shuffle_count = nil)
|
115
|
+
shuffle_count ||= size / 10
|
116
|
+
pairs = Hash[*(0..(size)).to_a.shuffle.first(shuffle_count)]
|
117
|
+
pairs.each do |from, to|
|
118
|
+
self[from], self[to] = self[to], self[from]
|
119
|
+
end
|
120
|
+
self
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "fails to deadlock while simultaneously deleting items from the same hierarchy" do
|
125
|
+
target = User.find_or_create_by_path((1..200).to_a.map { |ea| ea.to_s })
|
126
|
+
nodes_to_delete = target.self_and_ancestors.to_a.bad_shuffle!
|
127
|
+
destroyer_threads = @workers.times.map do
|
128
|
+
Thread.new do
|
129
|
+
ActiveRecord::Base.connection.reconnect!
|
130
|
+
begin
|
131
|
+
victim = nodes_to_delete.shift
|
132
|
+
victim.destroy if victim
|
133
|
+
end while !nodes_to_delete.empty?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
destroyer_threads.each { |ea| ea.join }
|
137
|
+
User.all.should be_empty
|
138
|
+
end
|
139
|
+
|
65
140
|
end unless parallelism_is_broken
|
data/spec/spec_helper.rb
CHANGED
@@ -31,7 +31,11 @@ end
|
|
31
31
|
ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(plugin_test_dir + "/db/database.yml")).result)
|
32
32
|
ActiveRecord::Base.establish_connection(ENV["DB"])
|
33
33
|
ActiveRecord::Migration.verbose = false
|
34
|
-
|
34
|
+
if ENV['NONUKES']
|
35
|
+
puts "skipping database creation"
|
36
|
+
else
|
37
|
+
require 'db/schema'
|
38
|
+
end
|
35
39
|
require 'support/models'
|
36
40
|
|
37
41
|
class Hash
|
data/spec/support/models.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'uuidtools'
|
2
2
|
|
3
3
|
class Tag < ActiveRecord::Base
|
4
|
-
acts_as_tree :dependent => :destroy, :order =>
|
4
|
+
acts_as_tree :dependent => :destroy, :order => :name
|
5
5
|
before_destroy :add_destroyed_tag
|
6
6
|
attr_accessible :name, :title if _ct.use_attr_accessible?
|
7
7
|
def to_s
|
data/spec/tag_examples.rb
CHANGED
@@ -429,6 +429,14 @@ shared_examples_for "Tag (without fixtures)" do
|
|
429
429
|
end
|
430
430
|
end
|
431
431
|
|
432
|
+
describe 'very deep trees' do
|
433
|
+
it 'should find_or_create very deep nodes' do
|
434
|
+
expected_ancestry_path = (1..200).to_a.map { |ea| ea.to_s }
|
435
|
+
target = tag_class.find_or_create_by_path(expected_ancestry_path)
|
436
|
+
target.ancestry_path.should == expected_ancestry_path
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
432
440
|
describe 'DOT rendering' do
|
433
441
|
it 'should render for an empty scope' do
|
434
442
|
tag_class.to_dot_digraph(tag_class.where("0=1")).should == "digraph G {\n}\n"
|
metadata
CHANGED
@@ -1,167 +1,167 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: closure_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.2.
|
4
|
+
version: 4.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 3.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 3.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: with_advisory_lock
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 0.0.9
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.0.9
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - '>='
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: yard
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - '>='
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - '>='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: fuubar
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - '>='
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - '>='
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rspec-rails
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - '>='
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - '>='
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: mysql2
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - '>='
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '0'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- -
|
122
|
+
- - '>='
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: pg
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - '>='
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- -
|
136
|
+
- - '>='
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: sqlite3
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- -
|
143
|
+
- - '>='
|
144
144
|
- !ruby/object:Gem::Version
|
145
145
|
version: '0'
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- -
|
150
|
+
- - '>='
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: uuidtools
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
|
-
- -
|
157
|
+
- - '>='
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: '0'
|
160
160
|
type: :development
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
|
-
- -
|
164
|
+
- - '>='
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
167
|
description: Easily and efficiently make your ActiveRecord model support hierarchies
|
@@ -212,17 +212,17 @@ require_paths:
|
|
212
212
|
- lib
|
213
213
|
required_ruby_version: !ruby/object:Gem::Requirement
|
214
214
|
requirements:
|
215
|
-
- -
|
215
|
+
- - '>='
|
216
216
|
- !ruby/object:Gem::Version
|
217
217
|
version: '0'
|
218
218
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
219
219
|
requirements:
|
220
|
-
- -
|
220
|
+
- - '>='
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
223
|
requirements: []
|
224
224
|
rubyforge_project:
|
225
|
-
rubygems_version: 2.0.
|
225
|
+
rubygems_version: 2.0.2
|
226
226
|
signing_key:
|
227
227
|
specification_version: 4
|
228
228
|
summary: Easily and efficiently make your ActiveRecord model support hierarchies
|