graphql 0.0.1 → 0.0.2

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +78 -9
  3. data/lib/graphql/call.rb +7 -0
  4. data/lib/graphql/connection.rb +44 -0
  5. data/lib/graphql/field.rb +117 -0
  6. data/lib/graphql/field_definer.rb +29 -0
  7. data/lib/graphql/introspection/call_node.rb +13 -0
  8. data/lib/graphql/introspection/connection.rb +9 -0
  9. data/lib/graphql/introspection/field_node.rb +19 -0
  10. data/lib/graphql/introspection/root_call_argument_node.rb +5 -0
  11. data/lib/graphql/introspection/root_call_node.rb +16 -0
  12. data/lib/graphql/introspection/schema_call.rb +8 -0
  13. data/lib/graphql/introspection/schema_node.rb +17 -0
  14. data/lib/graphql/introspection/type_call.rb +8 -0
  15. data/lib/graphql/introspection/type_node.rb +16 -0
  16. data/lib/graphql/node.rb +141 -34
  17. data/lib/graphql/parser.rb +19 -8
  18. data/lib/graphql/query.rb +64 -21
  19. data/lib/graphql/root_call.rb +176 -0
  20. data/lib/graphql/root_call_argument.rb +8 -0
  21. data/lib/graphql/root_call_argument_definer.rb +20 -0
  22. data/lib/graphql/schema.rb +99 -0
  23. data/lib/graphql/syntax/call.rb +3 -12
  24. data/lib/graphql/syntax/field.rb +4 -2
  25. data/lib/graphql/syntax/node.rb +3 -10
  26. data/lib/graphql/syntax/query.rb +7 -0
  27. data/lib/graphql/syntax/variable.rb +7 -0
  28. data/lib/graphql/transform.rb +14 -5
  29. data/lib/graphql/types/boolean_field.rb +3 -0
  30. data/lib/graphql/types/connection_field.rb +30 -0
  31. data/lib/graphql/types/cursor_field.rb +9 -0
  32. data/lib/graphql/types/number_field.rb +3 -0
  33. data/lib/graphql/types/object_field.rb +8 -0
  34. data/lib/graphql/types/string_field.rb +3 -0
  35. data/lib/graphql/types/type_field.rb +6 -0
  36. data/lib/graphql/version.rb +3 -0
  37. data/readme.md +142 -10
  38. data/spec/graphql/field_spec.rb +66 -0
  39. data/spec/graphql/node_spec.rb +68 -0
  40. data/spec/graphql/parser_spec.rb +75 -25
  41. data/spec/graphql/query_spec.rb +185 -83
  42. data/spec/graphql/root_call_spec.rb +55 -0
  43. data/spec/graphql/schema_spec.rb +128 -0
  44. data/spec/graphql/transform_spec.rb +124 -39
  45. data/spec/spec_helper.rb +2 -1
  46. data/spec/support/dummy_app.rb +43 -23
  47. data/spec/support/nodes.rb +145 -32
  48. metadata +78 -16
  49. data/lib/graphql/collection_edge.rb +0 -62
  50. data/lib/graphql/syntax/edge.rb +0 -12
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe GraphQL::Field do
5
+ let(:owner) { OpenStruct.new(name: "TestOwner")}
6
+ let(:field) { GraphQL::Field.create_class(name: "high_fives", type: :number, owner_class: owner).new(query: {}) }
7
+
8
+ describe '#name' do
9
+ it 'is present' do
10
+ assert_equal field.name, "high_fives"
11
+ end
12
+ end
13
+
14
+ describe '#method' do
15
+ it 'defaults to name' do
16
+ assert_equal "high_fives", field.method
17
+ end
18
+ end
19
+
20
+ describe '.call' do
21
+ let(:content_field) { Nodes::PostNode.all_fields["content"] }
22
+ it 'doesnt register a call twice' do
23
+ assert_equal 3, content_field.calls.size
24
+ call = content_field.calls.first[1]
25
+ content_field.call(call.name, call.lambda)
26
+ content_field.call(call.name, call.lambda)
27
+ assert_equal 3, content_field.calls.size
28
+ end
29
+ end
30
+
31
+ describe '.to_s' do
32
+ it 'includes name' do
33
+ assert_match(/high_fives/, field.class.to_s)
34
+ end
35
+ it 'includes owner name' do
36
+ assert_match(/TestOwner/, field.class.to_s)
37
+ end
38
+ end
39
+
40
+ describe '__type__' do
41
+ let(:query_string) { "type(post) { fields { edges { node { name, type, calls { edges { node { name } }} } } } } "}
42
+ let(:query) { GraphQL::Query.new(query_string, context: {}) }
43
+ let(:result) { query.as_result }
44
+ let(:id_field) { result["post"]["fields"]["edges"][1]["node"] }
45
+ let(:title_field) { result["post"]["fields"]["edges"][2]["node"] }
46
+ let(:comments_field) { result["post"]["fields"]["edges"][5]["node"] }
47
+ let(:content_field) { result["post"]["fields"]["edges"][3]["node"] }
48
+
49
+ it 'has name' do
50
+ assert_equal "id", id_field["name"]
51
+ assert_equal "title", title_field["name"]
52
+ assert_equal "comments", comments_field["name"]
53
+ end
54
+
55
+ it 'has type' do
56
+ assert_equal "number", id_field["type"]
57
+ assert_equal "string", title_field["type"]
58
+ assert_equal "connection", comments_field["type"]
59
+ end
60
+
61
+ it 'has calls' do
62
+ assert_equal 3, content_field["calls"]["edges"].length
63
+ assert_equal ["from", "for", "select"], content_field["calls"]["edges"].map {|c| c["node"]["name"] }
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Node do
4
+ let(:query_string) { "type(post) { name, description, fields { count, edges { node { name, description }}} }"}
5
+ let(:result) { GraphQL::Query.new(query_string).as_result}
6
+
7
+ describe '__type__' do
8
+ let(:title_field) { result["post"]["fields"]["edges"].find {|e| e["node"]["name"] == "title"}["node"] }
9
+ it 'has name' do
10
+ assert_equal "post", result["post"]["name"]
11
+ end
12
+
13
+ it 'has description' do
14
+ assert_equal "A blog post entry", result["post"]["description"]
15
+ end
16
+
17
+ it 'has fields' do
18
+ assert_equal 8, result["post"]["fields"]["count"]
19
+ assert_equal({ "name" => "title", "description" => nil}, title_field)
20
+ end
21
+
22
+ describe 'getting the __type__ field' do
23
+ before do
24
+ @post = Post.create(id: 155, content: "Hello world")
25
+ end
26
+
27
+ after do
28
+ @post.destroy
29
+ end
30
+
31
+ let(:query_string) { "post(155) { __type__ { name, fields { count } } }"}
32
+
33
+ it 'exposes the type' do
34
+ assert_equal "post", result["155"]["__type__"]["name"]
35
+ assert_equal 8, result["155"]["__type__"]["fields"]["count"]
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '.node_name' do
41
+ let(:query_string) { "type(upvote) { name }"}
42
+
43
+ it 'overrides __type__.name' do
44
+ assert_equal "upvote", result["upvote"]["name"]
45
+ end
46
+ end
47
+
48
+ describe '.field' do
49
+ it 'doesnt add the field twice if you call it twice' do
50
+ assert_equal 5, Nodes::CommentNode.all_fields.size
51
+ Nodes::CommentNode.field.number(:id)
52
+ Nodes::CommentNode.field.number(:id)
53
+ assert_equal 5, Nodes::CommentNode.all_fields.size
54
+ Nodes::CommentNode.remove_field(:id)
55
+ end
56
+
57
+ describe 'type:' do
58
+ it 'uses symbols to find built-ins' do
59
+ id_field = Nodes::CommentNode.all_fields["id"]
60
+ assert id_field.superclass == GraphQL::Types::NumberField
61
+ end
62
+ it 'uses the provided class as a superclass' do
63
+ letters_field = Nodes::CommentNode.all_fields["letters"]
64
+ assert letters_field.superclass == Nodes::LetterSelectionField
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,33 +1,59 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe GraphQL::Parser do
4
- let(:node_name) { "" }
5
- let(:fields) { "id, name"}
6
- let(:query) { "#{node_name} { #{fields} }"}
7
4
  let(:parser) { GraphQL::PARSER }
8
5
 
6
+ describe 'query' do
7
+ let(:query) { parser.query }
8
+ it 'parses node-only' do
9
+ assert query.parse_with_debug("node(4) { id, name } ")
10
+ end
11
+ it 'parses node and variables' do
12
+ assert query.parse_with_debug(%{
13
+ like_page(<page>) {
14
+ page { id }
15
+ }
16
+ <page>: {
17
+ "page": {"id": 1},
18
+ "person" : { "id", 4}
19
+ }
20
+ <other>: {
21
+ "page": {"id": 1},
22
+ "person" : { "id", 4}
23
+ }
24
+ })
25
+ end
26
+ end
9
27
  describe 'field' do
10
28
  let(:field) { parser.field }
11
29
  it 'finds words' do
12
- assert field.parse_with_debug("name")
13
30
  assert field.parse_with_debug("date_of_birth")
14
31
  end
15
- end
16
32
 
17
- describe 'edge' do
18
- let(:edge) { parser.edge }
33
+ it 'finds aliases' do
34
+ assert field.parse_with_debug("name as moniker")
35
+ end
19
36
 
20
37
  it 'finds calls on fields' do
21
- assert edge.parse_with_debug("friends.first(1) {
22
- count,
23
- edges {
24
- cursor,
25
- node {
26
- name
27
- }
28
- }
29
- }
30
- ")
38
+ assert field.parse_with_debug("url.site(www).upcase()")
39
+ end
40
+
41
+ describe 'fields that return objects' do
42
+ it 'finds them' do
43
+ assert field.parse_with_debug("birthdate { month, year }")
44
+ end
45
+
46
+ it 'finds them with aliases' do
47
+ assert field.parse_with_debug("birthdate as d_o_b { month, year }")
48
+ end
49
+
50
+ it 'finds them with calls' do
51
+ assert field.parse_with_debug("friends.after(123) { count { edges { node { id } } } }")
52
+ end
53
+
54
+ it 'finds them with calls and aliases' do
55
+ assert field.parse_with_debug("friends.after(123) as pals { count { edges { node { id } } } }")
56
+ end
31
57
  end
32
58
  end
33
59
 
@@ -37,15 +63,13 @@ describe GraphQL::Parser do
37
63
  assert call.parse_with_debug("node(123)")
38
64
  assert call.parse_with_debug("viewer()")
39
65
  end
40
- end
41
66
 
42
- describe 'call_chain' do
43
- let(:call_chain) { parser.call_chain }
44
- it 'finds deep calls' do
45
- assert call_chain.parse_with_debug("friends.after(123).first(2)")
67
+ it 'finds calls with multiple arguments' do
68
+ assert call.parse_with_debug("node(4, 6)")
46
69
  end
47
- it 'finds chain with no calls' do
48
- assert call_chain.parse_with_debug("friends")
70
+
71
+ it 'finds calls with variables' do
72
+ assert call.parse_with_debug("like_page(<page>)")
49
73
  end
50
74
  end
51
75
 
@@ -71,7 +95,8 @@ describe GraphQL::Parser do
71
95
  end
72
96
 
73
97
  it 'parses nested nodes' do
74
- assert node.parse_with_debug("node(someone)
98
+ assert node.parse_with_debug("
99
+ node(someone)
75
100
  {
76
101
  id,
77
102
  name,
@@ -86,4 +111,29 @@ describe GraphQL::Parser do
86
111
  ")
87
112
  end
88
113
  end
114
+
115
+ describe 'variable' do
116
+ let(:variable) { parser.variable }
117
+
118
+ it 'gets scalar variables' do
119
+ assert variable.parse_with_debug(%{<some_number>: 888})
120
+ assert variable.parse_with_debug(%{<some_string>: my_string})
121
+ end
122
+ it 'gets json variables' do
123
+ assert variable.parse_with_debug(%{<my_input>: {"key": "value"}})
124
+ end
125
+
126
+ it 'gets variables with nesting' do
127
+ assert variable.parse_with_debug(%{
128
+ <my_input>: {
129
+ "key": "value",
130
+ "1": 2,
131
+ "true": false,
132
+ "nested": {
133
+ "key" : "value"
134
+ }
135
+ }
136
+ })
137
+ end
138
+ end
89
139
  end
@@ -2,153 +2,255 @@ require 'spec_helper'
2
2
 
3
3
  describe GraphQL::Query do
4
4
  let(:query_string) { "post(123) { title, content } "}
5
- let(:namespace) { Nodes }
6
- let(:query) { GraphQL::Query.new(query_string, namespace: namespace) }
5
+ let(:context) { Context.new(person_name: "Han Solo") }
6
+ let(:query) { GraphQL::Query.new(query_string, context: context) }
7
+ let(:result) { query.as_result }
7
8
 
8
- describe '#root' do
9
- it 'contains the first node of the graph' do
10
- assert query.root.is_a?(GraphQL::Syntax::Node)
11
- end
9
+ before do
10
+ @post = Post.create(id: 123, content: "So many great things", title: "My great post", published_at: Date.new(2010,1,4))
11
+ @comment1 = Comment.create(id: 444, post_id: 123, content: "I agree", rating: 5)
12
+ @comment2 = Comment.create(id: 445, post_id: 123, content: "I disagree", rating: 1)
13
+ @like1 = Like.create(id: 991, post_id: 123)
14
+ @like2 = Like.create(id: 992, post_id: 123)
15
+ end
16
+
17
+ after do
18
+ @post.destroy
19
+ @comment1.destroy
20
+ @comment2.destroy
21
+ @like1.destroy
22
+ @like2.destroy
12
23
  end
13
24
 
14
- describe '#to_json' do
15
- before do
16
- @post = Post.create(id: 123, content: "So many great things", title: "My great post")
17
- @comment1 = Comment.create(id: 444, post_id: 123, content: "I agree")
18
- @comment2 = Comment.create(id: 445, post_id: 123, content: "I disagree")
25
+ describe '#as_result' do
26
+ it 'finds fields that delegate to a target' do
27
+ assert_equal result, {"123" => {"title" => "My great post", "content" => "So many great things"}}
19
28
  end
20
29
 
21
- after do
22
- @post.destroy
23
- @comment1.destroy
24
- @comment2.destroy
30
+ describe 'with multiple roots' do
31
+ let(:query_string) { "comment(444, 445) { content } "}
32
+ it 'adds each as a key-value of the response' do
33
+ assert_equal ["444", "445"], result.keys
34
+ end
25
35
  end
26
36
 
27
- it 'performs the root node call' do
28
- assert_send([Nodes::PostNode, :call, "123"])
29
- query.to_json
37
+ describe 'when accessing fields that return objects' do
38
+ describe 'when making calls on the field' do
39
+ let(:query_string) { "post(123) { published_at.minus_days(200) { year } }"}
40
+ it 'returns the modified value' do
41
+ assert_equal 2009, result["123"]["published_at"]["year"]
42
+ end
43
+ end
44
+ describe 'when requesting more fields' do
45
+ let(:query_string) { "post(123) { published_at { month, year } }"}
46
+ it 'returns those fields' do
47
+ assert_equal({"month" => 1, "year" => 2010}, result["123"]["published_at"])
48
+ end
49
+ end
30
50
  end
51
+ describe 'when aliasing things' do
52
+ let(:query_string) { "post(123) { title as headline, content as what_it_says }"}
31
53
 
32
- it 'finds fields that delegate to a target' do
33
- assert_equal query.to_json, {
34
- "123" => {
35
- "title" => "My great post",
36
- "content" => "So many great things"
37
- }
38
- }
54
+ it 'applies aliases to fields' do
55
+ assert_equal @post.title, result["123"]["headline"]
56
+ assert_equal @post.content, result["123"]["what_it_says"]
57
+ end
58
+
59
+ it 'applies aliases to edges' # dunno the syntax yet
39
60
  end
40
61
 
41
62
  describe 'when requesting fields defined on the node' do
42
- let(:query_string) { "post(123) { teaser } "}
63
+ let(:query_string) { "post(123) { length } "}
43
64
  it 'finds fields defined on the node' do
44
- assert_equal query.to_json, { "123" => { "teaser" => @post.content[0,10] + "..."}}
65
+ assert_equal 20, result["123"]["length"]
45
66
  end
46
67
  end
47
68
 
69
+ describe 'when accessing custom fields' do
70
+ let(:query_string) { "comment(444) { letters }"}
71
+ it 'uses the custom field' do
72
+ assert_equal "I agree", result["444"]["letters"]
73
+ end
74
+
75
+ describe 'when making calls on fields' do
76
+ let(:query_string) { "comment(444) {
77
+ letters.select(4, 3),
78
+ letters.from(3).for(2) as snippet
79
+ }"}
80
+
81
+ it 'works with aliases' do
82
+ assert result["444"]["snippet"].present?
83
+ end
84
+
85
+ it 'applies calls' do
86
+ assert_equal "gr", result["444"]["snippet"]
87
+ end
88
+
89
+ it 'applies calls with multiple arguments' do
90
+ assert_equal "ree", result["444"]["letters"]
91
+ end
92
+ end
93
+
94
+ describe 'when requesting fields overriden on a child class' do
95
+ let(:query_string) { 'thumb_up(991) { id }'}
96
+ it 'uses the child implementation' do
97
+ assert_equal '991991', result["991991"]["id"]
98
+ end
99
+ end
100
+ end
48
101
 
49
102
  describe 'when requesting an undefined field' do
50
103
  let(:query_string) { "post(123) { destroy } "}
51
104
  it 'raises a FieldNotDefined error' do
52
- assert_raises(GraphQL::FieldNotDefinedError) { query.to_json }
105
+ assert_raises(GraphQL::FieldNotDefinedError) { query.as_result }
53
106
  assert(Post.find(123).present?)
54
107
  end
55
108
  end
56
109
 
57
110
  describe 'when the root call doesnt have an argument' do
58
- let(:query_string) { "viewer() { name }"}
59
- it 'calls the node with nil' do
60
- assert_send([Nodes::ViewerNode, :call, nil])
61
- query.to_json
111
+ let(:query_string) { "context() { person_name }"}
112
+ it 'calls the node with no arguments' do
113
+ assert_equal "Han Solo", result["context"]["person_name"]
62
114
  end
63
115
  end
64
116
 
65
117
  describe 'when requesting a collection' do
66
118
  let(:query_string) { "post(123) {
67
119
  title,
68
- comments {
69
- count,
70
- edges {
71
- cursor,
72
- node {
73
- content
74
- }
75
- }
76
- }
120
+ comments { count, edges { cursor, node { content } } }
77
121
  }"}
122
+
78
123
  it 'returns collection data' do
79
- assert_equal query.to_json, {
124
+ assert_equal result, {
80
125
  "123" => {
81
126
  "title" => "My great post",
82
127
  "comments" => {
83
128
  "count" => 2,
84
129
  "edges" => [
85
- {
86
- "cursor" => "444",
87
- "node" => {
88
- "content" => "I agree"
89
- }
90
- },
91
- {
92
- "cursor" => "445",
93
- "node" => {
94
- "content" => "I disagree"
95
- }
96
- }
130
+ { "cursor" => "444", "node" => {"content" => "I agree"} },
131
+ { "cursor" => "445", "node" => {"content" => "I disagree"}}
97
132
  ]
98
- }
99
- }
100
- }
133
+ }}}
101
134
  end
102
135
  end
103
136
 
104
137
  describe 'when making calls on a collection' do
105
- let(:query_string) { "post(123) {
106
- comments.first(1) {
107
- edges { cursor, node { content } }
108
- }
109
- }"}
138
+ let(:query_string) { "post(123) { comments.first(1) { edges { cursor, node { content } } } }"}
110
139
 
111
140
  it 'executes those calls' do
112
- assert_equal query.to_json, {
141
+ assert_equal result, {
113
142
  "123" => {
114
143
  "comments" => {
115
144
  "edges" => [
116
- {
117
- "cursor" => "444",
118
- "node" => {
119
- "content" => "I agree"
120
- }
121
- }
145
+ { "cursor" => "444", "node" => { "content" => "I agree"} }
122
146
  ]
123
- }
124
- }
125
- }
147
+ }}}
126
148
  end
127
149
  end
128
150
 
129
151
  describe 'when making DEEP calls on a collection' do
130
- let(:query_string) { "post(123) {
131
- comments.after(444).first(1) {
152
+ let(:query_string) { "post(123) { comments.after(444).first(1) {
132
153
  edges { cursor, node { content } }
133
- }
134
- }"}
154
+ }}"}
135
155
 
136
156
  it 'executes those calls' do
137
- assert_equal query.to_json, {
157
+ assert_equal result, {
138
158
  "123" => {
139
159
  "comments" => {
140
160
  "edges" => [
141
161
  {
142
162
  "cursor" => "445",
143
- "node" => {
144
- "content" => "I disagree"
145
- }
163
+ "node" => { "content" => "I disagree"}
146
164
  }
147
165
  ]
148
- }
149
- }
150
- }
166
+ }}}
151
167
  end
152
168
  end
169
+
170
+ describe 'when requesting fields at collection-level' do
171
+ let(:query_string) { "post(123) { comments { average_rating } }"}
172
+
173
+ it 'executes those calls' do
174
+ assert_equal result, { "123" => { "comments" => { "average_rating" => 3 } } }
175
+ end
176
+ end
177
+
178
+ describe 'when making calls on node fields' do
179
+ let(:query_string) { "post(123) { comments { edges { node { letters.from(3).for(3) }} } }"}
180
+
181
+ it 'makes calls on the fields' do
182
+ assert_equal ["gre", "isa"], result["123"]["comments"]["edges"].map {|e| e["node"]["letters"] }
183
+ end
184
+ end
185
+
186
+ describe 'when requesting collection-level fields that dont exist' do
187
+ let(:query_string) { "post(123) { comments { bogus_field } }"}
188
+
189
+ it 'raises FieldNotDefined' do
190
+ assert_raises(GraphQL::FieldNotDefinedError) { query.as_result }
191
+ end
192
+ end
193
+ end
194
+
195
+ describe 'when requesting fields on a related object' do
196
+ let(:query_string) { "comment(444) { post { title } }"}
197
+ it 'finds fields on that object' do
198
+ assert_equal "My great post", result["444"]["post"]["title"]
199
+ end
200
+
201
+ describe 'when the object doesnt exist' do
202
+ before do
203
+ Post.all.map(&:destroy)
204
+ end
205
+
206
+ it 'blows_up' do # what _should_ this do?
207
+ assert_raises(RuntimeError) { result }
208
+ end
209
+ end
210
+ end
211
+
212
+ describe 'when edge classes were named explicitly' do
213
+ let(:query_string) { "post(123) { likes { any, edges { node { id } } } }"}
214
+
215
+ it 'gets node values' do
216
+ assert_equal ["991991","992992"], result["123"]["likes"]["edges"].map {|e| e["node"]["id"] }
217
+ end
218
+
219
+ it 'gets edge values' do
220
+ assert_equal true, result["123"]["likes"]["any"]
221
+ end
222
+ end
223
+
224
+ describe '#context' do
225
+ let(:query_string) { "context() { person_name }"}
226
+
227
+ it 'is accessible inside nodes' do
228
+ assert_equal({"context" => {"person_name" => "Han Solo"}}, result)
229
+ end
230
+
231
+ describe 'inside edges' do
232
+ let(:query_string) { "post(123) { comments { viewer_name_length } }"}
233
+ it 'is accessible' do
234
+ assert_equal 8, result["123"]["comments"]["viewer_name_length"]
235
+ end
236
+ end
237
+ end
238
+
239
+ describe 'parsing error' do
240
+ let(:query_string) { "\n\n<< bogus >>"}
241
+
242
+ it 'raises SyntaxError' do
243
+ assert_raises(GraphQL::SyntaxError) { result }
244
+ end
245
+
246
+ it 'contains line an character number' do
247
+ err = assert_raises(GraphQL::SyntaxError) { result }
248
+ assert_match(/1, 1/, err.to_s)
249
+ end
250
+
251
+ it 'contains sample of text' do
252
+ err = assert_raises(GraphQL::SyntaxError) { result }
253
+ assert_includes(err.to_s, "<< bogus >>")
254
+ end
153
255
  end
154
256
  end