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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NmE3ZGYxNDE5MjE5Y2FkMjMxNjY5YjVmMTZkYmM4MTY5ZGQ0NTUxZQ==
5
- data.tar.gz: !binary |-
6
- NGMyNmRiZGY1N2RiNGZiYTk1NzgzYzg4N2U2MDIxZWFlNjNmNjMxZg==
7
- !binary "U0hBNTEy":
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
  [![Build Status](https://secure.travis-ci.org/mceachen/closure_tree.png?branch=master)](http://travis-ci.org/mceachen/closure_tree)
9
9
  [![Gem Version](https://badge.fury.io/rb/closure_tree.png)](http://rubygems.org/gems/closure_tree)
10
10
  [![Code Climate](https://codeclimate.com/github/mceachen/closure_tree.png)](https://codeclimate.com/github/mceachen/closure_tree)
11
+ [![Dependency Status](https://gemnasium.com/mceachen/closure_tree.png)](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], :unique => true
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
  ```
@@ -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
- subpath = path.is_a?(Enumerable) ? path.dup : [path]
17
- child_name = subpath.shift
18
- return self unless child_name
19
- child = transaction do
20
- attrs = attributes.merge(_ct.name_sym => child_name)
21
- # shenanigans because children.create is bound to the superclass
22
- # (in the case of polymorphism):
23
- self.children.where(attrs).first || begin
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? ? scoped : joins(:ancestor_hierarchies).
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
- path.reverse.each_with_index do |ea, idx|
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
- scope = scope.where("#{last_joined_table}.#{_ct.parent_column_name}" => parent_id)
133
- scope.first
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 changes[_ct.parent_column_name] &&
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
- delete_hierarchy_references
35
- if _ct.options[:dependent] == :nullify
36
- self.class.find(self.id).children.each { |c| c.rebuild! }
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
- # The crazy double-wrapped sub-subselect works around MySQL's limitation of subselects on the same table that is being mutated.
61
- # It shouldn't affect performance of postgresql.
62
- # See http://dev.mysql.com/doc/refman/5.0/en/subquery-errors.html
63
- # Also: PostgreSQL doesn't support INNER JOIN on DELETE, so we can't use that.
64
- _ct.connection.execute <<-SQL
65
- DELETE FROM #{_ct.quoted_hierarchy_table_name}
66
- WHERE descendant_id IN (
67
- SELECT DISTINCT descendant_id
68
- FROM (SELECT descendant_id
69
- FROM #{_ct.quoted_hierarchy_table_name}
70
- WHERE ancestor_id = #{_ct.quote(id)}
71
- ) AS x )
72
- OR descendant_id = #{_ct.quote(id)}
73
- SQL
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
@@ -106,9 +106,7 @@ module ClosureTree
106
106
  def with_advisory_lock(&block)
107
107
  if options[:with_advisory_lock]
108
108
  model_class.with_advisory_lock("closure_tree") do
109
- model_class.transaction do
110
- yield
111
- end
109
+ transaction { yield }
112
110
  end
113
111
  else
114
112
  yield
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = Gem::Version.new("4.2.4") unless defined?(::ClosureTree::VERSION)
2
+ VERSION = Gem::Version.new("4.2.5") unless defined?(::ClosureTree::VERSION)
3
3
  end
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|
@@ -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
- # SQLite doesn't support parallel writes
7
- ENV["DB"] =~ /sqlite/
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].compact)
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
- start_time = Time.now.to_i + 2
28
- @times = @iterations.times.collect { |ea| start_time + (ea * 2) }
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.should_receive(:with_advisory_lock).any_number_of_times { |lock_name, &block| block.call }
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
- require 'db/schema'
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
@@ -1,7 +1,7 @@
1
1
  require 'uuidtools'
2
2
 
3
3
  class Tag < ActiveRecord::Base
4
- acts_as_tree :dependent => :destroy, :order => "name"
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
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-06-27 00:00:00.000000000 Z
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.3
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