palsy 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 60fb2ba51b992f7b83ce4d94f0999bbdd57ba8a7
4
+ data.tar.gz: b52c06df6875bade0c18b0261c19ee2c1c27c51a
5
+ SHA512:
6
+ metadata.gz: ba0b46d264bd49e24ed3780827705ab7a17a7cf1145a6e81beb903ec2fcfcd79f986ea7c5e564b48b5ac26fc8183cd2f2b8d42a7bccb2a83cb4fce63d395a84a
7
+ data.tar.gz: aa00113a932da3527a871e940493d099698c42d025b8d6093f27ccb1b1a95397c67705465c649964494ce5c44354e6cbdb3781a66aed4973eaf2b9b4d16abdf9
@@ -1,3 +1,6 @@
1
+ * 0.0.4 - April 6, 2013
2
+ * This takes the changes in 0.0.3 and improves robustness over high-volume
3
+ concurrency and improves transaction isolation for single operations.
1
4
  * 0.0.3 - April 2, 2013
2
5
  * Ensure exclusive transactions are being used for all operations. This
3
6
  should make it a little more viable for non-MRI things.
@@ -1,6 +1,7 @@
1
1
  require 'sqlite3'
2
2
  require 'singleton'
3
3
  require 'delegate'
4
+ require 'thread'
4
5
  require "palsy/version"
5
6
 
6
7
  # Present ruby core data structures in a manner similar to perl's tie backed by a
@@ -67,6 +68,7 @@ class Palsy < DelegateClass(SQLite3::Database)
67
68
  # working with the rest of the library.
68
69
  #
69
70
  def initialize
71
+ @palsy_lock = Mutex.new
70
72
  super(connect)
71
73
  end
72
74
 
@@ -88,15 +90,72 @@ class Palsy < DelegateClass(SQLite3::Database)
88
90
  end
89
91
  end
90
92
 
93
+ # Override the explicit lock for the current thread. For things that generate
94
+ # queries, will not attempt to acquire a mutex, will not attempt to run a
95
+ # transaction. Will just run the block. See #with_t for information on lock
96
+ # semantics.
97
+ #
98
+ # For Ruby 2.0 users and above: If you wrap your palsy operations in #no_lock
99
+ # on the current thread, #with_t will not try to use a mutex lock or
100
+ # transaction.
101
+ #
102
+ # This is useful if you want to use palsy in a signal handler:
103
+ #
104
+ # trap("INFO") do
105
+ # Palsy.instance.no_lock do
106
+ # some_palsy_thing
107
+ # end
108
+ # end
109
+ #
110
+ # The caveat is that any writes done in this region will not be isolated and
111
+ # will likely cause problems.
112
+ #
113
+ def no_lock
114
+ retval = nil
115
+ Thread.current[:palsy_no_lock] = true
116
+ retval = yield
117
+ Thread.current[:palsy_no_lock] = false
118
+ return retval
119
+ end
120
+
121
+ #
122
+ # Start a mutex-wrapped exclusive transaction, then execute the block. If
123
+ # we're already the owner of the transaction, just execute.
124
+ #
125
+ # This is used by most things in Palsy. If you want to step around the lock
126
+ # (necessary for specific situations, and has certain caveats), take a look
127
+ # at #no_lock.
128
+ #
129
+ def with_t
130
+ result = nil
131
+ tc = Thread.current
132
+
133
+ if tc[:lock] or tc[:palsy_no_lock]
134
+ result = yield
135
+ else
136
+ @palsy_lock.synchronize do
137
+ begin
138
+ tc[:lock] = @palsy_lock
139
+ transaction(:exclusive) { result = yield }
140
+ rescue StandardError => e
141
+ rollback if transaction_active?
142
+ raise e
143
+ ensure
144
+ tc[:lock] = nil
145
+ end
146
+ end
147
+ end
148
+
149
+ return result
150
+ end
151
+
91
152
  #
92
153
  # Execute this query in an exclusive transaction.
93
154
  #
94
155
  def execute_t(query, args=[], &block)
95
156
  result = nil
96
157
 
97
- transaction(:exclusive) do
98
- result = execute(query, args, &block)
99
- end
158
+ with_t { result = execute(query, args, &block) }
100
159
 
101
160
  return result
102
161
  end
@@ -49,16 +49,23 @@ class Palsy
49
49
  # Add a value to the head of the list.
50
50
  #
51
51
  def unshift(val)
52
- replace([val] + to_a)
52
+ @db.with_t do
53
+ replace([val] + to_a)
54
+ end
53
55
  end
54
56
 
55
57
  #
56
58
  # Helper method for mutators.
57
59
  #
58
60
  def mutate(meth)
59
- a = to_a
60
- val = a.send(meth)
61
- replace(a)
61
+ val = nil
62
+
63
+ @db.with_t do
64
+ a = to_a
65
+ val = a.send(meth)
66
+ replace(a)
67
+ end
68
+
62
69
  return val
63
70
  end
64
71
 
@@ -81,15 +88,17 @@ class Palsy
81
88
  # like an Enumerable.
82
89
  #
83
90
  def replace(ary)
84
- clear
85
- return if ary.count == 0
91
+ @db.with_t do
92
+ clear
93
+ return if ary.count == 0
86
94
 
87
- value_string = ("(?, ?)," * ary.count).chop
95
+ value_string = ("(?, ?)," * ary.count).chop
88
96
 
89
- @db.execute_t(
90
- "insert into #{@table_name} (name, value) values #{value_string}",
91
- ary.map { |x| [@object_name, Marshal.dump(x)] }.flatten
92
- )
97
+ @db.execute_t(
98
+ "insert into #{@table_name} (name, value) values #{value_string}",
99
+ ary.map { |x| [@object_name, Marshal.dump(x)] }.flatten
100
+ )
101
+ end
93
102
  end
94
103
 
95
104
  #
@@ -49,18 +49,37 @@ class Palsy
49
49
  # database before attempting to write the new pair.
50
50
  #
51
51
  def []=(key, value)
52
- delete(key)
53
- @db.execute_t("insert into #{@table_name} (name, key, value) values (?, ?, ?)", [@object_name, Marshal.dump(key), Marshal.dump(value)])
52
+ @db.with_t do
53
+ delete(key)
54
+ @db.execute_t("insert into #{@table_name} (name, key, value) values (?, ?, ?)", [@object_name, Marshal.dump(key), Marshal.dump(value)])
55
+ end
54
56
  value
55
57
  end
56
58
 
59
+ #
60
+ # replace this map with the contents of a Hash.
61
+ #
62
+ def replace(hash)
63
+ @db.with_t do
64
+ clear
65
+
66
+ return if hash.keys.empty?
67
+
68
+ value_string = ("(?, ?, ?)," * hash.keys.count).chop
69
+
70
+ @db.execute_t("insert into #{@table_name} (name, key, value) values #{value_string}", hash.map { |key, value| [@object_name, Marshal.dump(key), Marshal.dump(value)] }.flatten)
71
+ end
72
+ end
73
+
57
74
  #
58
75
  # Successively yields key and value for each item in the map. Modifications
59
76
  # to either the key or value yielded will not persist.
60
77
  #
61
78
  def each
62
- keys.each do |key|
63
- yield key, self[key]
79
+ @db.with_t do
80
+ keys.each do |key|
81
+ yield key, self[key]
82
+ end
64
83
  end
65
84
  end
66
85
 
@@ -27,7 +27,13 @@ class Palsy
27
27
  # Get an object by referencing its key. Returns nil unless it exists.
28
28
  #
29
29
  def [](key)
30
- value = @db.execute_t("select value from #{@table_name} where key=?", [key]).first.first rescue nil
30
+ value = begin
31
+ ret = @db.execute_t("select value from #{@table_name} where key=?", [key])
32
+ (ret && ret.first) ? ret.first.first : nil
33
+ rescue SQLite3::Exception
34
+ nil
35
+ end
36
+
31
37
  return value && Marshal.load(value)
32
38
  end
33
39
 
@@ -35,8 +41,10 @@ class Palsy
35
41
  # Set an object. Returns the object. The object must be able to be Marshalled.
36
42
  #
37
43
  def []=(key, value)
38
- delete(key)
39
- @db.execute_t("insert into #{@table_name} (key, value) values (?, ?)", [key, Marshal.dump(value)])
44
+ @db.with_t do
45
+ delete(key)
46
+ @db.execute_t("insert into #{@table_name} (key, value) values (?, ?)", [key, Marshal.dump(value)])
47
+ end
40
48
  value
41
49
  end
42
50
 
@@ -41,8 +41,10 @@ class Palsy
41
41
  # avoid raising a constraint from sqlite.
42
42
  #
43
43
  def add(key)
44
- delete(key)
45
- @db.execute_t("insert into #{@table_name} (name, key) values (?, ?)", [@object_name, Marshal.dump(key)])
44
+ @db.with_t do
45
+ delete(key)
46
+ @db.execute_t("insert into #{@table_name} (name, key) values (?, ?)", [@object_name, Marshal.dump(key)])
47
+ end
46
48
  end
47
49
 
48
50
  #
@@ -73,13 +75,15 @@ class Palsy
73
75
  # set, or any Enumerable that has a set of unique values.
74
76
  #
75
77
  def replace(set)
76
- clear
78
+ @db.with_t do
79
+ clear
77
80
 
78
- return if set.empty?
81
+ return if set.empty?
79
82
 
80
- value_string = ("(?, ?)," * set.count).chop
83
+ value_string = ("(?, ?)," * set.count).chop
81
84
 
82
- @db.execute_t("insert into #{@table_name} (name, key) values #{value_string}", set.map { |x| [@object_name, Marshal.dump(x)] }.flatten)
85
+ @db.execute_t("insert into #{@table_name} (name, key) values #{value_string}", set.map { |x| [@object_name, Marshal.dump(x)] }.flatten)
86
+ end
83
87
  end
84
88
 
85
89
  #
@@ -1 +1 @@
1
- PalsyVersion = "0.0.3"
1
+ PalsyVersion = "0.0.4"
@@ -7,6 +7,7 @@ end
7
7
 
8
8
  require 'minitest/unit'
9
9
  require 'tempfile'
10
+ require 'timeout'
10
11
  require 'palsy'
11
12
 
12
13
  module Palsy::TestHelper
@@ -38,4 +38,19 @@ class TestInit < MiniTest::Unit::TestCase
38
38
  Palsy.reconnect
39
39
  assert_raises(NoMethodError) { Palsy.instance.closed? }
40
40
  end
41
+
42
+ def test_with_t
43
+ dbfile = new_dbfile
44
+ Palsy.change_db(dbfile.path)
45
+
46
+ (0..2).map do
47
+ Thread.new do
48
+ assert_raises(ArgumentError) do
49
+ Palsy.instance.with_t do
50
+ raise ArgumentError
51
+ end
52
+ end
53
+ end
54
+ end.map(&:join)
55
+ end
41
56
  end
@@ -15,6 +15,46 @@ class TestList < PalsyTypeTest
15
15
  t.each(&:join)
16
16
 
17
17
  assert_equal(syms.sort, @list.to_a.sort)
18
+
19
+ writer = Thread.new { loop { @list.push(rand.to_i) } }
20
+ reader = Thread.new { loop { @list.shift } }
21
+
22
+ begin
23
+ Timeout.timeout(10) do
24
+ writer.join
25
+ reader.join
26
+ end
27
+ rescue TimeoutError
28
+ pass "push and shift competed for 10 seconds without any exceptions"
29
+ writer.raise
30
+ reader.raise
31
+ writer.join rescue nil
32
+ reader.join rescue nil
33
+ end
34
+
35
+ replacer = Thread.new { loop { @list.replace(Set[1,2,3]) } }
36
+ clearer = Thread.new { loop { @list.clear } }
37
+
38
+ begin
39
+ Timeout.timeout(10) do
40
+ replacer.join
41
+ clearer.join
42
+ end
43
+ rescue TimeoutError
44
+ pass "replace and clear competed for 10 seconds without any exceptions"
45
+ replacer.raise
46
+ clearer.raise
47
+ replacer.join rescue nil
48
+ clearer.join rescue nil
49
+ end
50
+
51
+ refute(@list.db.transaction_active?)
52
+
53
+ # XXX there's no solid way of knowing what'll be in here without inserting
54
+ # locks which will actually defeat the point of the test. Check the type,
55
+ # which will cause an execution and ensure we don't have a stale
56
+ # transaction.
57
+ assert_kind_of(Array, @list.to_a)
18
58
  end
19
59
 
20
60
  def test_semantics
@@ -16,6 +16,46 @@ class TestMap < PalsyTypeTest
16
16
  t.each(&:join)
17
17
 
18
18
  assert_equal(@map.to_hash, args)
19
+
20
+ writer = Thread.new { loop { @map[rand.to_i] = 1 } }
21
+ reader = Thread.new { loop { @map.keys } }
22
+
23
+ begin
24
+ Timeout.timeout(10) do
25
+ writer.join
26
+ reader.join
27
+ end
28
+ rescue TimeoutError
29
+ pass "[] and keys competed for 10 seconds without any exceptions"
30
+ writer.raise
31
+ reader.raise
32
+ writer.join rescue nil
33
+ reader.join rescue nil
34
+ end
35
+
36
+ replacer = Thread.new { loop { @map.replace(Hash[[1,2,3].zip([2,3,4])]) } }
37
+ clearer = Thread.new { loop { @map.clear } }
38
+
39
+ begin
40
+ Timeout.timeout(10) do
41
+ replacer.join
42
+ clearer.join
43
+ end
44
+ rescue TimeoutError
45
+ pass "replace and clear competed for 10 seconds without any exceptions"
46
+ replacer.raise
47
+ clearer.raise
48
+ replacer.join rescue nil
49
+ clearer.join rescue nil
50
+ end
51
+
52
+ refute(@map.db.transaction_active?)
53
+
54
+ # XXX there's no solid way of knowing what'll be in here without inserting
55
+ # locks which will actually defeat the point of the test. Check the type,
56
+ # which will cause an execution and ensure we don't have a stale
57
+ # transaction.
58
+ assert_kind_of(Hash, @map.to_hash)
19
59
  end
20
60
 
21
61
  def test_inherits
@@ -86,4 +126,17 @@ class TestMap < PalsyTypeTest
86
126
  assert_equal(@map[k], v, "#each yields each value")
87
127
  end
88
128
  end
129
+
130
+ def test_replace
131
+ @map[1] = "foo"
132
+ assert_equal("foo", @map[1])
133
+
134
+ hash = {2 => "bar", 3 => "foo"}
135
+
136
+ @map.replace(hash)
137
+ refute_equal("foo", @map[1])
138
+ assert_equal("bar", @map[2])
139
+ assert_equal("foo", @map[3])
140
+ assert_equal(hash, @map.to_hash)
141
+ end
89
142
  end
@@ -14,6 +14,41 @@ class TestObject < PalsyTypeTest
14
14
  args.each do |k,v|
15
15
  assert_equal(obj[k], args[k])
16
16
  end
17
+
18
+ writer = Thread.new { loop { obj["1"] = "1" } }
19
+ reader = Thread.new { loop { obj["1"] } }
20
+
21
+ begin
22
+ Timeout.timeout(10) do
23
+ writer.join
24
+ reader.join
25
+ end
26
+ rescue TimeoutError
27
+ pass "[]= and [] competed for 10 seconds without any exceptions"
28
+ writer.raise
29
+ reader.raise
30
+ writer.join rescue nil
31
+ reader.join rescue nil
32
+ end
33
+
34
+ replacer = Thread.new { loop { obj[1] = 1 } }
35
+ clearer = Thread.new { loop { obj.delete(1) } }
36
+
37
+ begin
38
+ Timeout.timeout(10) do
39
+ replacer.join
40
+ clearer.join
41
+ end
42
+ rescue TimeoutError
43
+ pass "[]= and delete competed for 10 seconds without any exceptions"
44
+ replacer.raise
45
+ clearer.raise
46
+ replacer.join rescue nil
47
+ clearer.join rescue nil
48
+ end
49
+
50
+ refute(obj.db.transaction_active?)
51
+ obj.delete(1)
17
52
  end
18
53
 
19
54
  def test_index
@@ -15,6 +15,61 @@ class TestSet < PalsyTypeTest
15
15
  t.each(&:join)
16
16
 
17
17
  assert_equal(syms.sort, @set.to_a.sort)
18
+
19
+ writer = Thread.new { loop { @set.add(rand.to_i) } }
20
+ reader = Thread.new { loop { @set.keys } }
21
+
22
+ begin
23
+ Timeout.timeout(10) do
24
+ writer.join
25
+ reader.join
26
+ end
27
+ rescue TimeoutError
28
+ pass "add and keys competed for 10 seconds without any exceptions"
29
+ writer.raise
30
+ reader.raise
31
+ writer.join rescue nil
32
+ reader.join rescue nil
33
+ end
34
+
35
+ replacer = Thread.new { loop { @set.replace(Set[1,2,3]) } }
36
+ clearer = Thread.new { loop { @set.clear } }
37
+
38
+ begin
39
+ Timeout.timeout(10) do
40
+ replacer.join
41
+ clearer.join
42
+ end
43
+ rescue TimeoutError
44
+ pass "replace and clear competed for 10 seconds without any exceptions"
45
+ replacer.raise
46
+ clearer.raise
47
+ replacer.join rescue nil
48
+ clearer.join rescue nil
49
+ end
50
+
51
+ refute(@set.db.transaction_active?)
52
+
53
+ # XXX there's no solid way of knowing what'll be in here without inserting
54
+ # locks which will actually defeat the point of the test. Check the type,
55
+ # which will cause an execution and ensure we don't have a stale
56
+ # transaction.
57
+ assert_kind_of(Set, @set.to_set)
58
+ end
59
+
60
+ def test_enumerable_freebies
61
+ (0..10).each { |x| @set.add(x) }
62
+ real_set = @set.to_set
63
+ assert_kind_of(Set, real_set)
64
+ assert_equal(Set[*(0..10).to_a], real_set)
65
+ array = @set.to_a
66
+ assert_kind_of(Array, array)
67
+ assert_equal((0..10).to_a, array.sort)
68
+
69
+ t = (0..2).map { Thread.new { @set.to_set } }
70
+ t.each(&:join)
71
+
72
+ t = (0..2).map { Thread.new { @set.to_a } }
18
73
  end
19
74
 
20
75
  def test_semantics
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: palsy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
5
- prerelease:
4
+ version: 0.0.4
6
5
  platform: ruby
7
6
  authors:
8
7
  - Erik Hollensbe
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-04-02 00:00:00.000000000 Z
11
+ date: 2013-04-06 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: sqlite3
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
@@ -30,7 +27,6 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: minitest
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ~>
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ~>
44
39
  - !ruby/object:Gem::Version
@@ -46,23 +41,20 @@ dependencies:
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: rake
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - '>='
52
46
  - !ruby/object:Gem::Version
53
47
  version: '0'
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - '>='
60
53
  - !ruby/object:Gem::Version
61
54
  version: '0'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rdoc
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - ~>
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - ~>
76
67
  - !ruby/object:Gem::Version
@@ -78,39 +69,34 @@ dependencies:
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: simplecov
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
- - - ! '>='
73
+ - - '>='
84
74
  - !ruby/object:Gem::Version
85
75
  version: '0'
86
76
  type: :development
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
- - - ! '>='
80
+ - - '>='
92
81
  - !ruby/object:Gem::Version
93
82
  version: '0'
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: guard-minitest
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - '>='
100
88
  - !ruby/object:Gem::Version
101
89
  version: '0'
102
90
  type: :development
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ! '>='
94
+ - - '>='
108
95
  - !ruby/object:Gem::Version
109
96
  version: '0'
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: guard-rake
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
101
  - - ~>
116
102
  - !ruby/object:Gem::Version
@@ -118,7 +104,6 @@ dependencies:
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
108
  - - ~>
124
109
  - !ruby/object:Gem::Version
@@ -126,17 +111,15 @@ dependencies:
126
111
  - !ruby/object:Gem::Dependency
127
112
  name: rb-fsevent
128
113
  requirement: !ruby/object:Gem::Requirement
129
- none: false
130
114
  requirements:
131
- - - ! '>='
115
+ - - '>='
132
116
  - !ruby/object:Gem::Version
133
117
  version: '0'
134
118
  type: :development
135
119
  prerelease: false
136
120
  version_requirements: !ruby/object:Gem::Requirement
137
- none: false
138
121
  requirements:
139
- - - ! '>='
122
+ - - '>='
140
123
  - !ruby/object:Gem::Version
141
124
  version: '0'
142
125
  description: An extremely simple marshalling persistence layer for SQLite based on
@@ -174,33 +157,26 @@ files:
174
157
  - test/test_set.rb
175
158
  homepage: ''
176
159
  licenses: []
160
+ metadata: {}
177
161
  post_install_message:
178
162
  rdoc_options: []
179
163
  require_paths:
180
164
  - lib
181
165
  required_ruby_version: !ruby/object:Gem::Requirement
182
- none: false
183
166
  requirements:
184
- - - ! '>='
167
+ - - '>='
185
168
  - !ruby/object:Gem::Version
186
169
  version: '0'
187
- segments:
188
- - 0
189
- hash: 3156010217290385344
190
170
  required_rubygems_version: !ruby/object:Gem::Requirement
191
- none: false
192
171
  requirements:
193
- - - ! '>='
172
+ - - '>='
194
173
  - !ruby/object:Gem::Version
195
174
  version: '0'
196
- segments:
197
- - 0
198
- hash: 3156010217290385344
199
175
  requirements: []
200
176
  rubyforge_project:
201
- rubygems_version: 1.8.25
177
+ rubygems_version: 2.0.0
202
178
  signing_key:
203
- specification_version: 3
179
+ specification_version: 4
204
180
  summary: An extremely simple marshalling persistence layer for SQLite based on perl's
205
181
  tie()
206
182
  test_files: