red_grape 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Manifest.txt +25 -10
  2. data/README.txt +54 -5
  3. data/bin/redgrape +77 -0
  4. data/data/graph-example-2.xml +26380 -0
  5. data/lib/ext/array_ext.rb +41 -0
  6. data/lib/ext/object_ext.rb +16 -0
  7. data/lib/red_grape/edge.rb +4 -17
  8. data/lib/red_grape/{propertied_object.rb → element.rb} +26 -4
  9. data/lib/red_grape/graph.rb +75 -73
  10. data/lib/red_grape/path_group.rb +5 -9
  11. data/lib/red_grape/pipe/aggregate_pipe.rb +13 -0
  12. data/lib/red_grape/pipe/as_pipe.rb +3 -10
  13. data/lib/red_grape/pipe/back_pipe.rb +1 -8
  14. data/lib/red_grape/pipe/base.rb +51 -6
  15. data/lib/red_grape/pipe/cap_pipe.rb +16 -0
  16. data/lib/red_grape/pipe/context.rb +64 -3
  17. data/lib/red_grape/pipe/except_pipe.rb +15 -0
  18. data/lib/red_grape/pipe/fill_pipe.rb +19 -0
  19. data/lib/red_grape/pipe/filter_pipe.rb +1 -7
  20. data/lib/red_grape/pipe/group_count_pipe.rb +17 -0
  21. data/lib/red_grape/pipe/has_pipe.rb +24 -0
  22. data/lib/red_grape/pipe/if_then_else_pipe.rb +1 -7
  23. data/lib/red_grape/pipe/in_pipe.rb +12 -15
  24. data/lib/red_grape/pipe/in_v_pipe.rb +12 -0
  25. data/lib/red_grape/pipe/loop_pipe.rb +16 -25
  26. data/lib/red_grape/pipe/out_e_pipe.rb +25 -0
  27. data/lib/red_grape/pipe/out_pipe.rb +6 -15
  28. data/lib/red_grape/pipe/paths_pipe.rb +21 -9
  29. data/lib/red_grape/pipe/property_pipe.rb +2 -11
  30. data/lib/red_grape/pipe/retain_pipe.rb +16 -0
  31. data/lib/red_grape/pipe/transform_pipe.rb +3 -11
  32. data/lib/red_grape/property_description.rb +6 -0
  33. data/lib/red_grape/serializer/graphml_serializer.rb +73 -0
  34. data/lib/red_grape/vertex.rb +7 -20
  35. data/lib/red_grape.rb +15 -6
  36. data/test/{test_propertied_object.rb → test_element.rb} +5 -5
  37. data/test/test_graph.rb +10 -19
  38. data/test/test_on_the_nature_of_pipes.rb +13 -13
  39. data/test/test_pipe.rb +16 -0
  40. data/test/test_pipe_base.rb +17 -0
  41. data/test/test_traversal_patterns.rb +69 -0
  42. metadata +38 -22
  43. data/bin/red_grape +0 -3
  44. data/lib/red_grape/vertex_group.rb +0 -72
  45. data/test/test_red_grape.rb +0 -8
@@ -0,0 +1,41 @@
1
+ class Array
2
+ def pass_through(pipe, context)
3
+ loops = context.loops
4
+ map! do |e|
5
+ context.loops = loops
6
+ pipe.pass e._, context # TODO
7
+ end
8
+ context.loops = loops
9
+ normalize_for_graph
10
+ end
11
+
12
+ def normalize_for_graph
13
+ reject! {|e| e.nil? or (e.respond_to?(:empty?) and e.empty?)}
14
+ flatten!
15
+ self
16
+ end
17
+
18
+ def vertex_array?
19
+ all?{|e| e.is_a? RedGrape::Vertex}
20
+ end
21
+
22
+ def edge_array?
23
+ all?{|e| e.is_a? RedGrape::Edge}
24
+ end
25
+
26
+ def graph_item_array?
27
+ vertex_array? or edge_array?
28
+ end
29
+
30
+ alias method_missing_without_prop_pipe method_missing
31
+ def method_missing_with_prop_pipe(name, *args, &block)
32
+ if graph_item_array?
33
+ map! {|e| e.send name, *args, &block}
34
+ else
35
+ method_missing_without_prop_pipe name, *args, &block
36
+ end
37
+ end
38
+ alias method_missing method_missing_with_prop_pipe
39
+
40
+
41
+ end
@@ -0,0 +1,16 @@
1
+ class Object
2
+ def pass_through(pipe, context)
3
+ pipe.pass self, context
4
+ end
5
+
6
+ def _
7
+ ret = self.dup
8
+ ret.extend RedGrape::Pipe::Out
9
+ ret.extend RedGrape::Pipe::OutE
10
+ ret.extend RedGrape::Pipe::In
11
+ ret.extend RedGrape::Pipe::SideEffect
12
+ ret.extend RedGrape::Pipe::As
13
+ ret.extend RedGrape::Pipe::Fill
14
+ ret
15
+ end
16
+ end
@@ -1,8 +1,8 @@
1
- require 'red_grape/propertied_object'
1
+ require 'red_grape/element'
2
2
 
3
3
  module RedGrape
4
- class Edge < PropertiedObject
5
- attr_reader :source, :target, :label
4
+ class Edge < Element
5
+ attr_reader :id, :source, :target, :label
6
6
 
7
7
  def initialize(graph, id, source, target, label, opts={})
8
8
  super graph, opts
@@ -14,21 +14,8 @@ module RedGrape
14
14
  @label = label
15
15
  end
16
16
 
17
- def _id
18
- @id
19
- end
20
-
21
- def _source
22
- @source
23
- end
24
-
25
- def _target
26
- @target
27
- end
28
-
29
17
  def to_s
30
- #"e[id(#{_id}):v[#{@source._id}]-v[#{@target._id}]:#{@property}]"
31
- "e[#{_id}:v[#{@source._id}]-v[#{@target._id}]]"
18
+ "e[#{@id}][#{@source.id}-#{@label}->#{@target.id}]"
32
19
  end
33
20
  end
34
21
  end
@@ -1,11 +1,21 @@
1
1
  require 'red_grape/property_description'
2
2
 
3
3
  module RedGrape
4
- class PropertiedObject
4
+ class Element
5
5
  def initialize(graph, opts={})
6
6
  @graph = graph
7
7
  @property = {}
8
- @property_description = opts[:property_description] || {}
8
+ if opts[:property_description]
9
+ @property_description = opts[:property_description]
10
+ (opts[:property] || {}).each do |k, v|
11
+ self[k] = v
12
+ end
13
+ else
14
+ @property_description = {}
15
+ opts.each do |k, v|
16
+ self[k] = v
17
+ end
18
+ end
9
19
  @property_description.each do |k ,v|
10
20
  if v.is_a? Array
11
21
  v = PropertyDescription.new(*v)
@@ -17,6 +27,7 @@ module RedGrape
17
27
  end
18
28
  end
19
29
 
30
+ # set property value with type checking
20
31
  def set_property(kid, v)
21
32
  # TODO: should be refactored
22
33
  desc = @property_description[kid]
@@ -29,8 +40,8 @@ module RedGrape
29
40
  end
30
41
  end
31
42
 
43
+ # set property value without type checking
32
44
  def []=(k, v)
33
- # TODO: type check?
34
45
  @property[k.to_s] = v
35
46
  end
36
47
 
@@ -38,9 +49,20 @@ module RedGrape
38
49
  @property[k.to_s]
39
50
  end
40
51
 
41
- # TODO: $B$$$i$J$$!)(B
42
52
  def pass_through(pipe, context)
43
53
  pipe.pass self, context
44
54
  end
55
+
56
+ def copy(depth=nil)
57
+ self # TODO
58
+ end
59
+
60
+ def ==(obj)
61
+ self.class == obj.class && self.id == obj.id
62
+ end
63
+
64
+ def method_missing(name, *args, &block)
65
+ self[name.to_s] or raise NoMethodError.new(name.to_s)
66
+ end
45
67
  end
46
68
  end
@@ -1,57 +1,89 @@
1
- require 'stringio'
1
+ require 'nokogiri'
2
2
  require 'red_grape/vertex'
3
- require 'red_grape/vertex_group'
4
3
  require 'red_grape/edge'
5
- require 'red_grape/edge_group'
6
4
  require 'red_grape/property_description'
5
+ require 'red_grape/serializer/graphml_serializer'
7
6
 
8
7
  module RedGrape
9
8
  class Graph
10
- # TODO: https://github.com/tinkerpop/blueprints/blob/bed2e64010882be66bf3b46d3c3e4b4ef4f6f2d9/blueprints-core/src/main/java/com/tinkerpop/blueprints/pgm/impls/tg/TinkerGraphFactory.java
11
- NAMESPACES = {
12
- 'xmlns' => 'http://graphml.graphdrawing.org/xmlns',
13
- 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
14
- }
15
-
16
- class <<self
9
+ class << self
17
10
  def load(filename)
18
- if filename =~ /^<\?xml/
19
- self.new.load StringIO.new(filename)
20
- else
21
- self.new.load filename
11
+ self.new.load filename
12
+ end
13
+
14
+ def create_tinker_graph
15
+ self.new do |g|
16
+ v1 = g.add_vertex 1, name:'marko', age:29
17
+ v2 = g.add_vertex 2, name:'vadas', age:27
18
+ v3 = g.add_vertex 3, name:'lop', lang:'java'
19
+ v4 = g.add_vertex 4, name:'josh', age:32
20
+ v5 = g.add_vertex 5, name:'ripple', lang:'java'
21
+ v6 = g.add_vertex 6, name:'peter', age:35
22
+ g.add_edge 7, v1, v2, 'knows', weight:0.5
23
+ g.add_edge 8, v1, v4, 'knows', weight:1.0
24
+ g.add_edge 9, v1, v3, 'created', weight:0.4
25
+ g.add_edge 10, v4, v5, 'created', weight:1.0
26
+ g.add_edge 11, v4, v3, 'created', weight:0.4
27
+ g.add_edge 12, v6, v3, 'created', weight:0.2
22
28
  end
23
29
  end
24
30
  end
25
31
 
26
- def initialize
32
+ attr_accessor :serializer
33
+
34
+ def initialize(&block)
35
+ @serializer = Serializer::GraphMLSerializer.new self
27
36
  @vertices = {}
28
37
  @edges = {}
29
38
  @property_descriptions = {}
39
+ block.call self if block
30
40
  end
31
41
 
32
- def vertex(*id)
33
- if 1 < id.size
34
- VertexGroup.new(id.map{|i| @vertices[i.to_s]})
42
+ def items(type, *id)
43
+ items = case type
44
+ when :vertex, :vertices
45
+ @vertices
46
+ when :edge, :edges
47
+ @edges
48
+ else
49
+ raise ArgumentError.new('type should be :vertex or :edge')
50
+ end
51
+
52
+ if id.size == 2 # TODO: for the time being
53
+ items.values.select{|e| e[id[0]] == id[1]}
54
+ elsif 1 < id.size
55
+ id.map{|i| items[i.to_s]}
35
56
  elsif id.size == 0
36
- VertexGroup.new @vertices.values
57
+ items.values
37
58
  else
38
59
  case id.first
39
60
  when Array
40
- VertexGroup.new(id.first.map{|i| @vertices[i.to_s]})
61
+ id.first.map{|i| items[i.to_s]}
41
62
  when :all
42
- VertexGroup.new @vertices.values
63
+ items.values
43
64
  else
44
- @vertices[id.first.to_s]
65
+ items[id.first.to_s]
45
66
  end
46
67
  end
47
68
  end
48
- alias v vertex
49
- alias V vertex
50
69
 
51
- def edge(id)
52
- @edges[id.to_s]
70
+ def vertex(*id)
71
+ items :vertex, *id
72
+ end
73
+
74
+ def v(*id)
75
+ vertex(*id)._
53
76
  end
54
- alias e edge
77
+ alias V v
78
+
79
+ def edge(*id)
80
+ items :edge, *id
81
+ end
82
+
83
+ def e(*id)
84
+ edge(*id)._
85
+ end
86
+ alias E e
55
87
 
56
88
  def add_vertex(id, v=nil)
57
89
  if v
@@ -62,78 +94,48 @@ module RedGrape
62
94
  if id.is_a? Hash
63
95
  v = id
64
96
  id = v[:id] || v['id']
65
- else
97
+ elsif id.respond_to?(:id)
66
98
  v = id
67
- id = v._id
99
+ id = v.id
100
+ else
101
+ v = {}
68
102
  end
69
103
  v = Vertex.new self, id, v
70
104
  end
71
- raise ArgumentError.new 'invalid id' unless id == v._id
105
+ raise ArgumentError.new 'invalid id' unless id == v.id
72
106
 
73
107
  @vertices[id.to_s] = v
74
108
  end
75
109
 
76
- def add_edge(id, label, from, to)
110
+ def add_edge(id, from, to, label, opts={})
77
111
  edge = if id.is_a? Edge
78
112
  id
79
113
  else
80
114
  id = id.to_s
81
115
  from = self.vertex[from.to_s] unless from.is_a? Vertex
82
116
  to = self.vertex[to.to_s] unless to.is_a? Vertex
83
- add_vertex from unless self.vertex(from._id)
84
- add_vertex to unless self.vertex(to._id)
85
- Edge.new self, id, from, to, label
117
+ add_vertex from unless self.vertex(from.id)
118
+ add_vertex to unless self.vertex(to.id)
119
+ Edge.new self, id, from, to, label, opts
86
120
  end
87
- @edges[edge._id] = edge
121
+ @edges[edge.id] = edge
88
122
  end
89
123
 
90
- def load(file, type=:xml)
91
- file = File.open file if file.is_a? String
92
- case type
93
- when :xml
94
- parse_xml Nokogiri::XML(file)
95
- end
96
- self
124
+ def load(filename)
125
+ @serializer.load filename
97
126
  end
98
127
 
99
- # assumption: any sub-graph does not exist.
100
- def parse_xml(xml)
101
- nodes(xml, 'key').each do |key_elm|
102
- defaults = nodes key_elm, 'default'
103
- default = defaults.size == 0 ? nil : defaults.first.children.to_s
104
- prop = PropertyDescription.new key_elm['attr.name'], key_elm['attr.type'], default
105
- (@property_descriptions[key_elm['for']] ||= {})[key_elm['id']] = prop
106
- end
107
- nodes(xml, 'node').each do |node_elm|
108
- vertex = Vertex.new self, node_elm['id'],
109
- :property_description => @property_descriptions['node']
110
- data = nodes node_elm, 'data'
111
- data.each do |data_elm|
112
- vertex.set_property data_elm['key'], data_elm.children.first.to_s
113
- end
114
- @vertices[vertex._id] = vertex
115
- end
116
- nodes(xml, 'edge').each do |edge_elm|
117
- edge = Edge.new self, edge_elm['id'], edge_elm['source'], edge_elm['target'],
118
- edge_elm['label'], :property_description => @property_descriptions['edge']
119
- data = nodes edge_elm, 'data'
120
- data.each do |data_elm|
121
- edge.set_property data_elm['key'], data_elm.children.first.to_s
122
- end
123
- @edges[edge._id] = edge
124
- end
128
+ def save(file)
129
+ file = File.open file if file.is_a? String
130
+ @serializer.save file
125
131
  end
126
132
 
127
133
  def find(*args)
128
134
  Graph::Vertex.new
129
135
  end
130
136
 
131
- def nodes(xml, elm)
132
- xml.xpath(".//xmlns:#{elm}", NAMESPACES)
133
- end
134
-
135
137
  def to_s
136
- {:vertices => @vertices, :edges => @edges}.to_s
138
+ "redgrape[vertices:#{@vertices.size} edges:#{@edges.size}]"
137
139
  end
138
140
  end
139
141
  end
@@ -1,16 +1,12 @@
1
- #TODO delegate to an array
1
+ require 'forwardable'
2
+
2
3
  module RedGrape
3
4
  class PathGroup
5
+ extend Forwardable
6
+ def_delegators :@paths, :size, :to_s, :[], :first, :last
7
+
4
8
  def initialize(ary)
5
9
  @paths = ary.dup
6
10
  end
7
-
8
- def size
9
- @paths.size
10
- end
11
-
12
- def to_s
13
- @paths.to_s
14
- end
15
11
  end
16
12
  end
@@ -0,0 +1,13 @@
1
+ require 'red_grape/pipe/base'
2
+
3
+ module RedGrape
4
+ module Pipe
5
+ class AggregatePipe < Pipe::Base
6
+ def pass(obj, context)
7
+ key = self.opts.first
8
+ context.aggregate key, obj, self.next
9
+ obj
10
+ end
11
+ end
12
+ end
13
+ end
@@ -16,16 +16,9 @@ module RedGrape
16
16
  # TODO: why??
17
17
  context.mark! label
18
18
  obj
19
- when Vertex
20
- if self.last?
21
- context.mark! label
22
- obj
23
- else
24
- context.push_history obj do |ctx|
25
- context.mark! label
26
- obj.pass_through self.next, ctx
27
- end
28
- end
19
+ when Vertex, Array
20
+ context.mark! label, obj
21
+ obj.pass_through self.next, context
29
22
  else
30
23
  raise 'not yet'
31
24
  end
@@ -11,14 +11,7 @@ module RedGrape
11
11
  else
12
12
  context.mark label
13
13
  end
14
-
15
- if self.last?
16
- obj
17
- else
18
- context.push_history obj do |ctx|
19
- obj.pass_through self.next, ctx
20
- end
21
- end
14
+ pass_next context, obj
22
15
  end
23
16
  end
24
17
  end
@@ -2,6 +2,16 @@ require 'red_grape/pipe/context'
2
2
 
3
3
  module RedGrape
4
4
  module Pipe
5
+ @@auto_take = false
6
+
7
+ def self.auto_take
8
+ @@auto_take
9
+ end
10
+
11
+ def self.set_auto_take(val=true)
12
+ @@auto_take = val
13
+ end
14
+
5
15
  class Base
6
16
  attr_accessor :opts, :prev, :next, :value
7
17
 
@@ -30,15 +40,38 @@ module RedGrape
30
40
 
31
41
  def take
32
42
  if first?
33
- @prev.pass_through self, Pipe::Context.new
43
+ context = Context.new
44
+ val = @prev.pass_through self, context
45
+ if context.aggregating?
46
+ context.resume_from_aggregating
47
+ elsif context.grouping?
48
+ context.resume_from_grouping
49
+ else
50
+ val
51
+ end
34
52
  else
35
53
  @prev.take
36
54
  end
37
55
  end
38
56
 
57
+ def pass_next(context, pushed_obj, next_obj=nil, &block)
58
+ next_obj ||= pushed_obj
59
+ if self.last?
60
+ block.call if block
61
+ next_obj
62
+ elsif pushed_obj.nil?
63
+ block.call if block
64
+ next_obj.pass_through self.next, context
65
+ else
66
+ context.push_history pushed_obj do |ctx|
67
+ block.call if block
68
+ next_obj.pass_through self.next, ctx
69
+ end
70
+ end
71
+ end
72
+
39
73
  def to_s
40
- #TODO: flag
41
- take.to_s
74
+ Pipe.auto_take ? take.to_s : super
42
75
  end
43
76
 
44
77
  def to_a
@@ -54,7 +87,7 @@ module RedGrape
54
87
  def size
55
88
  len = 0
56
89
  pipe = self
57
- while pipe.is_a?(RedGrape::Pipe::Base) and pipe.prev
90
+ while pipe.is_a?(Pipe::Base) and pipe.prev
58
91
  len += 1
59
92
  pipe = pipe.prev
60
93
  end
@@ -66,15 +99,27 @@ module RedGrape
66
99
  self.class.new nil, @opts, &@block
67
100
  end
68
101
 
102
+ def copy(depth=nil)
103
+ obj = self.class.new nil, @opts, &@block
104
+ if depth.nil?
105
+ obj.prev = self.prev.copy
106
+ obj.prev.next = obj
107
+ elsif 0 < depth
108
+ obj.prev = self.prev.copy(depth - 1)
109
+ obj.prev.next = obj
110
+ end
111
+ obj
112
+ end
113
+
69
114
  def method_missing(name, *args, &block)
70
115
  class_name = "#{name.to_s.sub(/^./){$&.upcase}.gsub(/_(.)/){$1.upcase}}Pipe"
71
116
  args.unshift block if block
72
117
  pipe_class =
73
- if RedGrape::Pipe.const_defined? class_name
118
+ if Pipe.const_defined? class_name
74
119
  eval "RedGrape::Pipe::#{class_name}"
75
120
  else
76
121
  args.unshift name
77
- RedGrape::Pipe::PropertyPipe
122
+ PropertyPipe
78
123
  end
79
124
  @next = pipe_class.new self, *args
80
125
  end
@@ -0,0 +1,16 @@
1
+ require 'red_grape/pipe/base'
2
+
3
+ module RedGrape
4
+ module Pipe
5
+ class CapPipe < Pipe::Base
6
+ def pass(obj, context)
7
+ ret = {}
8
+ context.grouping_items.each do |e|
9
+ ret[e.first] ||= 0
10
+ ret[e.first] += 1
11
+ end
12
+ ret
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,11 +2,14 @@ module RedGrape
2
2
  module Pipe
3
3
  class Context
4
4
  attr_accessor :it, :loops
5
- attr_reader :history
5
+ attr_reader :history, :grouping_items
6
6
 
7
7
  def initialize
8
8
  @history = []
9
9
  @marks = {}
10
+ @aggregating_items = {}
11
+ @aggregated_items = {}
12
+ @grouping_items = []
10
13
  @loops = 1
11
14
  end
12
15
 
@@ -17,14 +20,72 @@ module RedGrape
17
20
  ret
18
21
  end
19
22
 
20
- def mark!(label)
21
- @marks[label] = @history.last
23
+ def mark!(label, val=nil)
24
+ @marks[label] = val || @history.last
22
25
  end
23
26
 
24
27
  def mark(label)
25
28
  @marks[label]
26
29
  end
27
30
 
31
+ def aggregated_items(key)
32
+ @aggregated_items[key.to_s.to_sym] ||= []
33
+ end
34
+
35
+ def aggregating_items(key)
36
+ @aggregating_items[key.to_s.to_sym] ||= []
37
+ end
38
+
39
+ def aggregate(key, val, next_pipe)
40
+ self.aggregating_items(key) << [val, next_pipe]
41
+ val
42
+ end
43
+
44
+ def aggregating?
45
+ not @aggregating_items.empty?
46
+ end
47
+
48
+ def resume_from_aggregating
49
+ ret = []
50
+ aggregating = @aggregating_items
51
+ @aggregating_items = {}
52
+ aggregating.each do |key, obj_and_pipes|
53
+ obj_and_pipes.each do |obj_and_pipe|
54
+ (@aggregated_items[key] ||= []) << obj_and_pipe.first
55
+ end
56
+ end
57
+
58
+ aggregating.each do |key, obj_and_pipes|
59
+ obj_and_pipes.each do |obj_and_pipe|
60
+ obj, pipe = *obj_and_pipe
61
+ push_history obj do |ctx|
62
+ ret << obj.pass_through(pipe, ctx)
63
+ end
64
+ end
65
+ end
66
+ ret.normalize_for_graph
67
+ end
68
+
69
+ def group(val, next_pipe)
70
+ @grouping_items << [val, next_pipe]
71
+ val
72
+ end
73
+
74
+ def grouping?
75
+ not @grouping_items.empty?
76
+ end
77
+
78
+ def resume_from_grouping
79
+ obj, pipe = *@grouping_items.first # TODO: is '.first' ok?
80
+ if pipe
81
+ push_history obj do |ctx|
82
+ obj.pass_through(pipe, ctx)
83
+ end
84
+ else
85
+ obj
86
+ end
87
+ end
88
+
28
89
  def eval(args={}, &block)
29
90
  args.each {|k, v| self.send "#{k}=", v}
30
91
  instance_eval(&block)
@@ -0,0 +1,15 @@
1
+ require 'red_grape/pipe/base'
2
+
3
+ module RedGrape
4
+ module Pipe
5
+ class ExceptPipe < Pipe::Base
6
+ def pass(obj, context)
7
+ if context.aggregated_items(self.opts.first).include? obj
8
+ nil
9
+ else
10
+ obj
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ require 'red_grape/pipe/base'
2
+
3
+ module RedGrape
4
+ module Pipe
5
+ module Fill
6
+ def fill(*opts)
7
+ FillPipe.new self, *opts
8
+ end
9
+ end
10
+
11
+ class FillPipe < Pipe::Base
12
+ def pass(obj, context)
13
+ container = self.opts.first
14
+ container << obj
15
+ obj
16
+ end
17
+ end
18
+ end
19
+ end