closure_tree 4.2.4 → 4.2.5

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