data_graph 1.0.0

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.
data/History ADDED
@@ -0,0 +1,3 @@
1
+ == 1.0.0 2010/07/20
2
+
3
+ Initial public release.
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 Pinnacol Assurance
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software. Except as contained in this
12
+ notice, the name(s) of the above copyright holders shall not be used in
13
+ advertising or otherwise to promote the sale, use or other dealings in this
14
+ Software without prior written authorization.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README ADDED
@@ -0,0 +1,48 @@
1
+ = DataGraph
2
+
3
+ Simplified eager loading for ActiveRecord
4
+
5
+ == Description
6
+
7
+ The default eager loading mechanism of ActiveRecord has numerous cases where
8
+ these two are not equivalent as you might expect:
9
+
10
+ Model.find(:first, :include => :assoc).assoc
11
+ Model.find(:first).assoc
12
+
13
+ As a result it gets tricky to make associations that work correctly via
14
+ include. Oftentimes too much data gets returned. DataGraph makes eager loading
15
+ easier by providing a way to declare and load a specific set of associated
16
+ data.
17
+
18
+ == Usage
19
+
20
+ DataGraph uses a syntax based on the serialization methods.
21
+
22
+ require 'data_graph'
23
+ graph = Model.data_graph(
24
+ :only => [:a, :b, :c],
25
+ :include => {
26
+ :assoc => {
27
+ :only => [:x, :y]
28
+ }})
29
+
30
+ data = graph.find(:first)
31
+ data.a # => 'A'
32
+ data.assoc.x # => 'X'
33
+ data.assoc.z # !> ActiveRecord::MissingAttributeError
34
+
35
+ Any number of associations may be specified this way, and to any nesting
36
+ depth. DataGraph always uses a 'one query per-association' strategy and never
37
+ reverts to left outer joins the way include sometimes will.
38
+
39
+ == Installation
40
+
41
+ DataGraph is available as a gem on {Gemcutter}[http://gemcutter.org/gems/data_graph]
42
+
43
+ % gem install data_graph
44
+
45
+ == Info
46
+
47
+ Developer:: {Simon Chiang}[http://bahuvrihi.wordpress.com]
48
+ License:: {MIT-Style}[link:files/License_txt.html]
@@ -0,0 +1,9 @@
1
+ require 'data_graph/graph'
2
+
3
+ module DataGraph
4
+ def data_graph(options={})
5
+ Graph.new(Node.new(self, options), options)
6
+ end
7
+ end
8
+
9
+ ActiveRecord::Base.extend(DataGraph)
@@ -0,0 +1,21 @@
1
+ require 'data_graph/linkage'
2
+
3
+ module DataGraph
4
+ class CpkLinkage < Linkage
5
+ def parent_id(record)
6
+ parent_columns.collect {|attribute| record.send(attribute) }
7
+ end
8
+
9
+ def child_id(record)
10
+ child_columns.collect {|attribute| record.send(attribute) }
11
+ end
12
+
13
+ def conditions(id_map)
14
+ condition = child_columns.collect {|col| "#{table_name}.#{connection.quote_column_name(col)} = ?" }.join(' AND ')
15
+ conditions = Array.new(id_map.length, condition)
16
+ conditions_str = "(#{conditions.join(') OR (')})"
17
+
18
+ id_map.keys.flatten.unshift(conditions_str)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,92 @@
1
+ require 'data_graph/node'
2
+
3
+ module DataGraph
4
+ class Graph
5
+ include Utils
6
+
7
+ attr_reader :model
8
+ attr_reader :aliases
9
+ attr_reader :paths
10
+ attr_reader :subsets
11
+ attr_reader :node
12
+
13
+ def initialize(node, options={})
14
+ aliases = options[:aliases]
15
+ subsets = options[:subsets]
16
+
17
+ @node = node
18
+ @nest_paths = node.nest_paths
19
+ @aliases = @node.aliases
20
+ @aliases.merge!(aliases) if aliases
21
+
22
+ @paths = {}
23
+ @subsets = {}
24
+
25
+ subsets = {
26
+ :default => '*',
27
+ :get => node.get_paths,
28
+ :set => node.set_paths
29
+ }.merge(subsets || {})
30
+
31
+ subsets.each_pair do |name, unresolved_paths|
32
+ resolved_paths = resolve(unresolved_paths)
33
+ @paths[name] = resolved_paths
34
+ @subsets[name] = node.only(resolved_paths)
35
+ end
36
+ end
37
+
38
+ def find(*args)
39
+ node.find(*args)
40
+ end
41
+
42
+ def paginate(*args)
43
+ node.paginate(*args)
44
+ end
45
+
46
+ def only(paths)
47
+ node.only validate(:get, resolve(paths))
48
+ end
49
+
50
+ def except(paths)
51
+ node.only validate(:get, resolve(paths))
52
+ end
53
+
54
+ def resolve(paths)
55
+ paths = paths.collect {|path| aliases[path.to_s] || path }
56
+ paths.flatten!
57
+ paths.collect! {|path| path.to_s }
58
+ paths.uniq!
59
+ paths
60
+ end
61
+
62
+ def path(type)
63
+ paths[type] or raise "no such path: #{type.inspect}"
64
+ end
65
+
66
+ def subset(type, default_type = :default)
67
+ (subsets[type] || subsets[default_type]) or raise "no such subset: #{type.inspect}"
68
+ end
69
+
70
+ def validate(type, paths)
71
+ inaccessible_paths = paths - path(type)
72
+ unless inaccessible_paths.empty?
73
+ raise InaccessiblePathError.new(inaccessible_paths)
74
+ end
75
+ paths
76
+ end
77
+
78
+ def validate_attrs(type, attrs)
79
+ validate(type, patherize_attrs(attrs, @nest_paths))
80
+ attrs
81
+ end
82
+ end
83
+
84
+ class InaccessiblePathError < RuntimeError
85
+ attr_reader :paths
86
+
87
+ def initialize(paths)
88
+ @paths = paths
89
+ super "inaccesible: #{paths.inspect}"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,127 @@
1
+ require 'data_graph/utils'
2
+
3
+ module DataGraph
4
+ class Linkage
5
+ include Utils
6
+
7
+ attr_reader :macro
8
+ attr_reader :name
9
+ attr_reader :through
10
+ attr_reader :table_name
11
+ attr_reader :connection
12
+ attr_reader :parent_columns
13
+ attr_reader :child_columns
14
+ attr_reader :child_node
15
+
16
+ def initialize(assoc, options={})
17
+ @macro = assoc.macro
18
+ @name = assoc.name
19
+ @through = nil
20
+
21
+ case macro
22
+ when :belongs_to
23
+ @parent_columns = foreign_key(assoc)
24
+ @child_columns = reference_key(assoc)
25
+ when :has_many, :has_one
26
+ if through_assoc = assoc.through_reflection
27
+ @through = assoc.source_reflection.name
28
+
29
+ assoc = through_assoc
30
+ options = {:only => [], :include => {@through => options}}
31
+ end
32
+
33
+ @parent_columns = reference_key(assoc)
34
+ @child_columns = foreign_key(assoc)
35
+ else
36
+ raise "currently unsupported association macro: #{macro}"
37
+ end
38
+
39
+ klass = assoc.klass
40
+ @child_node = Node.new(assoc.klass, options)
41
+ @table_name = klass.table_name
42
+ @connection = klass.connection
43
+ end
44
+
45
+ def node
46
+ through ? child_node[through] : child_node
47
+ end
48
+
49
+ def parent_id(record)
50
+ record.send parent_columns.at(0)
51
+ end
52
+
53
+ def child_id(record)
54
+ record.send child_columns.at(0)
55
+ end
56
+
57
+ def conditions(id_map)
58
+ ["#{table_name}.#{connection.quote_column_name(child_columns.at(0))} IN (?)", id_map.keys.flatten]
59
+ end
60
+
61
+ def link(parents)
62
+ id_map = Hash.new {|hash, key| hash[key] = [] }
63
+
64
+ parents = arrayify(parents)
65
+ parents.each do |parent|
66
+ id_map[parent_id(parent)] << parent
67
+ end
68
+
69
+ children = child_node.find(:all,
70
+ :select => child_columns,
71
+ :conditions => conditions(id_map))
72
+ visited = []
73
+
74
+ arrayify(children).each do |child|
75
+ id_map[child_id(child)].each do |parent|
76
+ visited << parent
77
+ set_child(parent, child)
78
+ end
79
+ end
80
+
81
+ if macro == :has_many && through
82
+ visited.each do |parent|
83
+ parent.send(name).uniq!
84
+ end
85
+ end
86
+
87
+ (parents - visited).each do |parent|
88
+ set_child(parent, nil)
89
+ end
90
+
91
+ children
92
+ end
93
+
94
+ def inherit(method_name, paths)
95
+ dup.inherit!(method_name, paths)
96
+ end
97
+
98
+ def inherit!(method_name, paths)
99
+ paths = paths.collect {|path| "#{through}.#{path}"} if through
100
+ @child_node = @child_node.send(method_name, paths)
101
+ self
102
+ end
103
+
104
+ private
105
+
106
+ def arrayify(obj) # :nodoc:
107
+ obj.kind_of?(Array) ? obj : [obj]
108
+ end
109
+
110
+ def set_child(parent, child) # :nodoc:
111
+ if child && through
112
+ child = child.send(through).target
113
+ end
114
+
115
+ case macro
116
+ when :belongs_to, :has_one
117
+ parent.send("set_#{name}_target", child)
118
+ when :has_many
119
+ association_proxy = parent.send(name)
120
+ association_proxy.loaded
121
+ association_proxy.target.push(child) if child
122
+ else
123
+ # should never get here...
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,290 @@
1
+ require 'data_graph/cpk_linkage'
2
+
3
+ module DataGraph
4
+
5
+ #-- Treated as immutable once created.
6
+ class Node
7
+ include Utils
8
+
9
+ attr_reader :model
10
+ attr_reader :column_names
11
+ attr_reader :method_names
12
+ attr_reader :linkages
13
+ attr_reader :always_columns
14
+
15
+ def initialize(model, options={})
16
+ @model = model
17
+ self.options = options
18
+ end
19
+
20
+ def associations
21
+ linkages.keys
22
+ end
23
+
24
+ def [](name)
25
+ linkage = linkages[name.to_s]
26
+ linkage ? linkage.node : nil
27
+ end
28
+
29
+ def paths
30
+ paths = column_names + method_names
31
+ linkages.each_pair do |name, linkage|
32
+ linkage.node.paths.each do |path|
33
+ paths << "#{name}.#{path}"
34
+ end
35
+ end
36
+
37
+ paths
38
+ end
39
+
40
+ def get_paths
41
+ paths = primary_keys(model) + column_names + method_names
42
+ linkages.each_pair do |name, linkage|
43
+ paths << name
44
+ paths.concat linkage.parent_columns
45
+ (linkage.node.get_paths + linkage.child_columns).each do |path|
46
+ paths << "#{name}.#{path}"
47
+ end
48
+ end
49
+
50
+ paths.uniq!
51
+ paths
52
+ end
53
+
54
+ def set_paths
55
+ paths = primary_keys(model) + column_names + method_names
56
+ nested_attributes = model.nested_attributes_options
57
+ linkages.each_pair do |name, linkage|
58
+ next unless nested_attributes.has_key?(name.to_sym)
59
+
60
+ attributes = linkage.node.set_paths
61
+ attributes += ActiveRecord::NestedAttributes::UNASSIGNABLE_KEYS
62
+ attributes.each do |path|
63
+ paths << "#{name}_attributes.#{path}"
64
+ end
65
+ end
66
+
67
+ paths.uniq!
68
+ paths
69
+ end
70
+
71
+ def aliases
72
+ aliases = {'*' => column_names.dup}
73
+
74
+ linkages.each_pair do |name, linkage|
75
+ linkage.node.aliases.each_pair do |als, paths|
76
+ aliases["#{name}.#{als}"] = paths.collect {|path| "#{name}.#{path}" }
77
+ end
78
+ end
79
+
80
+ aliases
81
+ end
82
+
83
+ def nest_paths
84
+ paths = []
85
+
86
+ linkages.each_pair do |name, linkage|
87
+ if linkage.macro == :has_many
88
+ paths << "#{name}_attributes"
89
+ end
90
+
91
+ linkage.node.nest_paths.each do |path|
92
+ paths << "#{name}.#{path}"
93
+ end
94
+ end
95
+
96
+ paths
97
+ end
98
+
99
+ def options
100
+ associations = {}
101
+ linkages.each_pair do |name, linkage|
102
+ associations[name.to_sym] = linkage.node.options
103
+ end
104
+
105
+ # note a new options hash must be generated because serialization is
106
+ # destructive to the hash (although not the values)
107
+
108
+ {
109
+ :only => column_names,
110
+ :methods => method_names,
111
+ :include => associations
112
+ }
113
+ end
114
+
115
+ def options=(options)
116
+ unless options.kind_of?(Hash)
117
+ raise "not a hash: #{options.inspect}"
118
+ end
119
+ @column_names = parse_columns(options)
120
+ @method_names = parse_methods(options)
121
+ @linkages = parse_linkages(options)
122
+ @always_columns = parse_always(options)
123
+ end
124
+
125
+ def find(*args)
126
+ link model.find(*find_args(args))
127
+ end
128
+
129
+ def paginate(*args)
130
+ link model.paginate(*find_args(args))
131
+ end
132
+
133
+ def find_args(args=[])
134
+ args << {} unless args.last.kind_of?(Hash)
135
+ scope(args.last)
136
+ args
137
+ end
138
+
139
+ def scope(options={})
140
+ columns = arrayify(options[:select]) + column_names + always_columns
141
+ linkages.each_value {|linkage| columns.concat linkage.parent_columns }
142
+ columns.uniq!
143
+
144
+ options[:select] = columns.join(',')
145
+ options
146
+ end
147
+
148
+ def link(records)
149
+ linkages.each_value do |linkage|
150
+ linkage.link(records)
151
+ end
152
+
153
+ records
154
+ end
155
+
156
+ def only!(paths)
157
+ attr_paths, nest_paths = partition(paths)
158
+ source, target = linkages, {}
159
+
160
+ attr_paths.each do |name|
161
+ if linkage = source[name]
162
+ target[name] = linkage
163
+ end
164
+ end
165
+
166
+ nest_paths.each_pair do |name, paths|
167
+ if linkage = source[name]
168
+ target[name] = linkage.inherit(:only, paths)
169
+ end
170
+ end
171
+
172
+ @column_names &= attr_paths
173
+ @method_names &= attr_paths
174
+ @linkages = target
175
+
176
+ self
177
+ end
178
+
179
+ def only(paths)
180
+ dup.only!(paths)
181
+ end
182
+
183
+ def except!(paths)
184
+ attr_paths, nest_paths = partition(paths)
185
+ source, target = linkages, {}
186
+
187
+ (attr_paths - nest_paths.keys).each do |path|
188
+ source.delete(path)
189
+ end
190
+
191
+ nest_paths.each_pair do |name, paths|
192
+ if linkage = source[name]
193
+ target[name] = linkage.inherit(:except, paths)
194
+ end
195
+ end
196
+
197
+ @column_names -= attr_paths
198
+ @method_names -= attr_paths
199
+ @linkages = target
200
+
201
+ self
202
+ end
203
+
204
+ def except(paths)
205
+ dup.except!(paths)
206
+ end
207
+
208
+ private
209
+
210
+ def arrayify(array)
211
+ case array
212
+ when Array then array
213
+ when nil then []
214
+ else [array]
215
+ end.collect! {|obj| obj.to_s }
216
+ end
217
+
218
+ def parse_columns(options)
219
+ attributes = model.column_names
220
+
221
+ case
222
+ when options.has_key?(:except)
223
+ if options.has_key?(:only)
224
+ raise "only and except are both specified: #{options.inspect}"
225
+ end
226
+
227
+ except = options[:except]
228
+ attributes - arrayify(except)
229
+
230
+ when options.has_key?(:only)
231
+ only = options[:only]
232
+ arrayify(only) & attributes
233
+
234
+ else
235
+ attributes.dup
236
+ end
237
+ end
238
+
239
+ def parse_methods(options)
240
+ arrayify(options[:methods])
241
+ end
242
+
243
+ def hashify(hash)
244
+ case hash
245
+ when Hash # default
246
+ hash.symbolize_keys
247
+ when Array # an array of identifiers {:include => [:a, :b]}
248
+ hash.inject({}) {|h, k| h[k.to_sym] = {}; h}
249
+ else # a bare identifier {:include => :a}
250
+ {hash.to_sym => {}}
251
+ end
252
+ end
253
+
254
+ def parse_linkages(options)
255
+ linkages = {}
256
+
257
+ hashify(options[:include] || {}).each_pair do |name, options|
258
+ next unless assoc = model.reflect_on_association(name)
259
+ linkage = cpk?(assoc) ? CpkLinkage : Linkage
260
+ linkages[name.to_s] = linkage.new(assoc, options)
261
+ end
262
+
263
+ linkages
264
+ end
265
+
266
+ def parse_always(options)
267
+ always_columns = primary_keys(model).collect {|key| key.to_s }
268
+ always_columns.concat arrayify(options[:always])
269
+ always_columns.uniq!
270
+ always_columns
271
+ end
272
+
273
+ def partition(paths)
274
+ attrs = []
275
+ nested_attrs = {}
276
+
277
+ paths.each do |path|
278
+ head, tail = path.split('.', 2)
279
+
280
+ if tail
281
+ (nested_attrs[head] ||= []) << tail
282
+ else
283
+ attrs << head
284
+ end
285
+ end
286
+
287
+ [attrs, nested_attrs]
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,59 @@
1
+ require 'active_record'
2
+ ActiveRecord.load_all!
3
+
4
+ require 'composite_primary_keys'
5
+
6
+ module DataGraph
7
+ module Utils
8
+ module_function
9
+
10
+ def primary_keys(model)
11
+ model.respond_to?(:primary_keys) ? model.primary_keys : [model.primary_key]
12
+ end
13
+
14
+ def foreign_key(assoc)
15
+ # actually returns options[:foreign_key], or the default foreign key
16
+ foreign_key = assoc.primary_key_name
17
+
18
+ # cpk returns a csv string
19
+ foreign_key.to_s.split(',')
20
+ end
21
+
22
+ def reference_key(assoc)
23
+ primary_key = assoc.options[:primary_key] || primary_keys(assoc.macro == :belongs_to ? assoc.klass : assoc.active_record)
24
+ primary_key.kind_of?(Array) ? primary_key.collect {|key| key.to_s } : primary_key.to_s.split(',')
25
+ end
26
+
27
+ def cpk?(assoc)
28
+ assoc = assoc.through_reflection if assoc.through_reflection
29
+ assoc.primary_key_name.to_s.include?(',')
30
+ end
31
+
32
+ def patherize_attrs(attrs, nest_paths=[], paths=[], prefix='')
33
+ attrs.each_pair do |key, value|
34
+ case key
35
+ when String, Symbol
36
+ path = "#{prefix}#{key}"
37
+
38
+ if nest_paths.include?(path)
39
+ value = value.values
40
+ end
41
+
42
+ case value
43
+ when Hash
44
+ patherize_attrs(value, nest_paths, paths, "#{path}.")
45
+ when Array
46
+ next_prefix = "#{path}."
47
+ value.each {|hash| patherize_attrs(hash, nest_paths, paths, next_prefix) }
48
+ else
49
+ paths << path
50
+ end
51
+ else
52
+ raise "unexpected attribute key: #{key.inspect}"
53
+ end
54
+ end
55
+
56
+ paths
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,7 @@
1
+ module DataGraph
2
+ MAJOR = 1
3
+ MINOR = 0
4
+ TINY = 0
5
+
6
+ VERSION="#{MAJOR}.#{MINOR}.#{TINY}"
7
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: data_graph
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Simon Chiang
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-07-20 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 3
30
+ - 5
31
+ version: 2.3.5
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: composite_primary_keys
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 2
43
+ - 3
44
+ - 5
45
+ - 1
46
+ version: 2.3.5.1
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: sqlite3-ruby
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 1
58
+ - 2
59
+ - 5
60
+ version: 1.2.5
61
+ type: :development
62
+ version_requirements: *id003
63
+ description:
64
+ email: simon.a.chiang@gmail.com
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files:
70
+ - History
71
+ - README
72
+ - License.txt
73
+ files:
74
+ - lib/data_graph.rb
75
+ - lib/data_graph/cpk_linkage.rb
76
+ - lib/data_graph/graph.rb
77
+ - lib/data_graph/linkage.rb
78
+ - lib/data_graph/node.rb
79
+ - lib/data_graph/utils.rb
80
+ - lib/data_graph/version.rb
81
+ - History
82
+ - README
83
+ - License.txt
84
+ has_rdoc: true
85
+ homepage: ""
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options:
90
+ - --main
91
+ - README
92
+ - -S
93
+ - -N
94
+ - --title
95
+ - Data Graph
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ requirements: []
113
+
114
+ rubyforge_project: ""
115
+ rubygems_version: 1.3.6
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: ""
119
+ test_files: []
120
+