akismet 0.1.2 → 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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jonah Burke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,68 @@
1
+ Akismet
2
+ =======
3
+
4
+ A Ruby client for the Akismet API.
5
+
6
+ Instantiate an `Akismet::Client` with your API key and home page URL. Then
7
+ call `verify_key`, `check_comment`, `submit_ham`, or `submit_spam`.
8
+
9
+ Use `Akismet::Client.open` or `Akismet::Client#open` to submit multiple
10
+ requests over a single TCP connection.
11
+
12
+ See lib/akismet/client.rb for more documentation or generate docs with YARD.
13
+
14
+ Verify an API key
15
+ -----------------
16
+
17
+ Akismet::Client.new( 'apikey123', 'http://jonahb.com' ).verify_key
18
+
19
+
20
+ Check whether a comment is spam
21
+ -------------------------------
22
+
23
+ client = Akismet::Client.new( 'apikey123',
24
+ 'http://jonahb.com',
25
+ :app_name => 'jonahb.com',
26
+ :app_version => '1.0' )
27
+
28
+ # assumes variables comment, post_url, request (a racklike HTTP request)
29
+ spam = client.comment_check( request.remote_ip,
30
+ request.user_agent,
31
+ :content_type => 'comment',
32
+ :referrer => request.headers[ 'HTTP_REFERER' ],
33
+ :permalink => post_url,
34
+ :comment_author => comment.author,
35
+ :comment_author_email => comment.author_email,
36
+ :comment_content => comment.body )
37
+
38
+ if spam
39
+ # ...
40
+ end
41
+
42
+ Submit a batch of checks using a single TCP connection
43
+ ------------------------------------------------------
44
+
45
+ client = Akismet::Client.new( 'apikey123',
46
+ 'http://jonahb.com',
47
+ :app_name => 'jonahb.com',
48
+ :app_version => '1.0' )
49
+
50
+ begin
51
+ client.open
52
+ comments.each do |comment|
53
+ client.comment_check( ... ) # see example above
54
+ end
55
+ ensure
56
+ client.close
57
+ end
58
+
59
+ # ... or ...
60
+
61
+ Akismet::Client.open( 'apikey123',
62
+ 'http://jonahb.com',
63
+ :app_name => 'jonahb.com',
64
+ :app_version => '1.0' ) do |client|
65
+ comments.each do |comment|
66
+ client.comment_check( ... ) # see example above
67
+ end
68
+ end
@@ -1,12 +1,7 @@
1
- require File.dirname( __FILE__ ) + '/akismet/version'
2
- require File.dirname( __FILE__ ) + '/akismet/service'
3
- require File.dirname( __FILE__ ) + '/akismet/client'
4
-
5
- module Akismet
6
-
7
- # convenience method
8
- def self.verify_key( api_key, home_url )
9
- Service.new.verify_key api_key, home_url
10
- end
11
-
1
+ %w{
2
+ version
3
+ error
4
+ client
5
+ }.each do |file|
6
+ require File.join( File.dirname( __FILE__ ), 'akismet', file )
12
7
  end
@@ -1,41 +1,410 @@
1
+ require 'net/http'
2
+ require 'cgi'
3
+
1
4
  module Akismet
5
+
6
+ # A Ruby client for the Akismet API.
7
+ #
8
+ # @example
9
+ #
10
+ # # Verify an API key
11
+ # #
12
+ #
13
+ # Akismet::Client.new( 'apikey123', 'http://jonahb.com' ).verify_key
14
+ #
15
+ # @example
16
+ #
17
+ # # Check whether a comment is spam
18
+ # #
19
+ #
20
+ # client = Akismet::Client.new( 'apikey123',
21
+ # 'http://jonahb.com',
22
+ # :app_name => 'jonahb.com',
23
+ # :app_version => '1.0' )
24
+ #
25
+ # # assumes variables comment, post_url, request (a racklike HTTP request)
26
+ # spam = client.comment_check( request.remote_ip,
27
+ # request.user_agent,
28
+ # :content_type => 'comment',
29
+ # :referrer => request.headers[ 'HTTP_REFERER' ],
30
+ # :permalink => post_url,
31
+ # :comment_author => comment.author,
32
+ # :comment_author_email => comment.author_email,
33
+ # :comment_content => comment.body )
34
+ #
35
+ # if spam
36
+ # # ...
37
+ # end
38
+ #
39
+ # @example
40
+ #
41
+ # # Submit a batch of checks using a single TCP connection
42
+ # #
43
+ #
44
+ #
45
+ # client = Akismet::Client.new( 'apikey123',
46
+ # 'http://jonahb.com',
47
+ # :app_name => 'jonahb.com',
48
+ # :app_version => '1.0' )
49
+ #
50
+ # begin
51
+ # client.open
52
+ # comments.each do |comment|
53
+ # client.comment_check( ... ) # see example above
54
+ # end
55
+ # ensure
56
+ # client.close
57
+ # end
58
+ #
59
+ # # ... or ...
60
+ #
61
+ # Akismet::Client.open( 'apikey123',
62
+ # 'http://jonahb.com',
63
+ # :app_name => 'jonahb.com',
64
+ # :app_version => '1.0' ) do |client|
65
+ # comments.each do |comment|
66
+ # client.comment_check( ... ) # see example above
67
+ # end
68
+ # end
69
+ #
70
+ #
2
71
  class Client
3
72
 
4
- attr_accessor :api_key, :home_url
73
+ # The API key obtained at akismet.com.
74
+ # @return [String]
75
+ #
76
+ attr_reader :api_key
77
+
78
+
79
+ # The URL of the home page of the application making the request.
80
+ # @return [String]
81
+ #
82
+ attr_reader :home_url
83
+
84
+
85
+ # The name of the application making the request, e.g "jonahb.com".
86
+ # @return [String]
87
+ #
88
+ attr_reader :app_name
89
+
90
+
91
+ # The version of the application making the request, e.g. "1.0".
92
+ # @return [String]
93
+ #
94
+ attr_reader :app_version
5
95
 
6
- def initialize( api_key, home_url )
96
+
97
+ # @param [String] api_key
98
+ # The API key obtained at akismet.com.
99
+ # @param [String] home_url
100
+ # The URL of the home page of the application making the request.
101
+ # @option options [String] :app_name
102
+ # The name of the application making the request, e.g. "jonahb.com".
103
+ # Forms part of the User-Agent header submitted to Akismet.
104
+ # @option options [String] :app_version
105
+ # The version of the application making the request, e.g. "1.0". Forms
106
+ # part of the User-Agent header submitted to Akismet. Ignored if
107
+ # :app_name is not privded.
108
+ #
109
+ def initialize( api_key, home_url, options = {} )
7
110
  @api_key = api_key
8
111
  @home_url = home_url
9
- @service = Service.new
112
+ @app_name = options[ :app_name ]
113
+ @app_version = options[ :app_version ]
114
+ end
115
+
116
+
117
+ # Yields a new open Client, closing the Client when the block returns.
118
+ # Takes the same arguments as {#initialize}. Use for submitting a batch of
119
+ # requests using a single TCP and HTTP session.
120
+ #
121
+ # @yield [Akismet::Client]
122
+ # @return [nil]
123
+ # @see #initialize
124
+ # @see #open
125
+ #
126
+ def self.open( api_key, home_url, options = {} )
127
+ raise "Block required" unless block_given?
128
+ client = nil
129
+ begin
130
+ client = new( api_key, home_url, options )
131
+ client.open
132
+ yield client
133
+ ensure
134
+ client.close if client
135
+ end
136
+ nil
137
+ end
138
+
139
+
140
+ # Opens the client. You may use this method in combination with {#close}
141
+ # to submit a batch of requests using a single TCP and HTTP session.
142
+ #
143
+ # If you don't call this before calling {#comment_check}, {#submit_ham},
144
+ # or {#submit_spam}, a session will be created and opened for the duration
145
+ # of the call.
146
+ #
147
+ # Note that calls to {#verify_key} always create a session. This is due to
148
+ # a peculiarity of the Akismet API where {#verify_key} requests must be
149
+ # sent to a different host.
150
+ #
151
+ # @return [self]
152
+ # @raise [RuntimeError]
153
+ # The client is already open.
154
+ # @see #close
155
+ # @see Akismet::Client.open
156
+ #
157
+ def open
158
+ raise "Already open" if open?
159
+ @http_session = Net::HTTP.new( "#{ api_key }.rest.akismet.com", 80 )
160
+ @http_session.start
161
+ self
162
+ end
163
+
164
+
165
+ # Closes the Client.
166
+ # @return [self]
167
+ # @see #open
168
+ #
169
+ def close
170
+ @http_session.finish if open?
171
+ @http_session = nil
172
+ self
173
+ end
174
+
175
+
176
+ # Whether the Client is open.
177
+ # @return [boolean]
178
+ #
179
+ def open?
180
+ @http_session && @http_session.started?
10
181
  end
11
182
 
183
+
184
+ # Checks the validity of the API key.
185
+ # @return [boolean]
186
+ #
12
187
  def verify_key
13
- @service.verify_key @api_key, @home_url
188
+ response = Net::HTTP.start( 'rest.akismet.com', 80 ) do |session|
189
+ invoke( session, 'verify-key', :blog => home_url, :key => api_key )
190
+ end
191
+
192
+ unless %w{ valid invalid }.include?( response.body )
193
+ raise_with_response response
194
+ end
195
+
196
+ response.body == 'valid'
14
197
  end
15
198
 
16
- def check( user_ip, user_agent, params = {} )
17
- @service.comment_check @api_key,
18
- @home_url,
199
+
200
+ # Checks whether a comment is spam. You are encouraged the submit, in
201
+ # addition to the documented parameters, data about the client and the
202
+ # comment submission. For example, if the client is an HTTP server,
203
+ # include HTTP headers and environment variables.
204
+ #
205
+ # If the Client is not open, opens it for the duration of the call.
206
+ #
207
+ # @return [boolean]
208
+ # @raise [Akismet::Error]
209
+ # @param [String] user_ip
210
+ # The IP address of the submitter of the comment.
211
+ # @param [String] user_agent
212
+ # The user agent of the web browser submitting the comment. Typically
213
+ # the HTTP_USER_AGENT CGI variable. Not to be confused with the user
214
+ # agent of the Akismet library.
215
+ # @option params [String] :referrer
216
+ # The value of the HTTP_REFERER header. Note that the parameter is
217
+ # spelled with two consecutive 'r's.
218
+ # @option params [String] :permalink
219
+ # The permanent URL of the entry to which the comment pertains.
220
+ # @option params [String] :comment_type
221
+ # 'comment', 'trackback', 'pingback', or a made-up value like
222
+ # 'registration'
223
+ # @option params [String] :comment_author
224
+ # The name of the author of the comment.
225
+ # @option params [String] :comment_author_email
226
+ # The email address of the author of the comment.
227
+ # @option params [String] :comment_author_url
228
+ # A URL submitted with the comment.
229
+ # @option params [String] :comment_content
230
+ # The text of the comment.
231
+ #
232
+ def comment_check( user_ip, user_agent, params = {} )
233
+ response = invoke_comment_method( 'comment-check',
19
234
  user_ip,
20
235
  user_agent,
21
- params
236
+ params )
237
+
238
+ unless %w{ true false }.include?( response.body )
239
+ raise_with_response response
240
+ end
241
+
242
+ response.body == 'true'
22
243
  end
23
244
 
24
- def ham( user_ip, user_agent, params = {} )
25
- @service.submit_ham @api_key,
26
- @home_url,
245
+
246
+ # Submits a comment that has been identified as not-spam (ham). Takes
247
+ # the same arguments as {#comment_check}. If the Client is not open, opens
248
+ # it for the duration of the call.
249
+ #
250
+ # @return [nil]
251
+ # @raise [Akismet::Error]
252
+ # @see #comment_check
253
+ #
254
+ def submit_ham( user_ip, user_agent, params = {} )
255
+ response = invoke_comment_method( 'submit-ham',
27
256
  user_ip,
28
257
  user_agent,
29
- params
258
+ params )
259
+
260
+ unless response.body == 'Thanks for making the web a better place.'
261
+ raise_from_response response
262
+ end
263
+
264
+ nil
30
265
  end
31
266
 
32
- def spam( user_ip, user_agent, params = {} )
33
- @service.submit_spam @api_key,
34
- @home_url,
267
+
268
+ # Submits a comment that has been identified as spam. Takes the same
269
+ # arguments as {#comment_check}. If the Client is not open, opens it for
270
+ # the duration of the call.
271
+ #
272
+ # @return [nil]
273
+ # @raise [Akismet::Error]
274
+ # @see #comment_check
275
+ #
276
+ def submit_spam( user_ip, user_agent, params = {} )
277
+ response = invoke_comment_method( 'submit-spam',
35
278
  user_ip,
36
279
  user_agent,
37
- params
280
+ params )
281
+
282
+ unless response.body == 'Thanks for making the web a better place.'
283
+ raise_from_response response
284
+ end
285
+
286
+ nil
287
+ end
288
+
289
+
290
+ private
291
+
292
+
293
+ # Yields an HTTP session to the given block. Uses this instance's open
294
+ # session if any; otherwise opens one and closes it when the block
295
+ # returns.
296
+ # @yield [Net::HTTP]
297
+ #
298
+ def in_http_session
299
+ raise "Block required" unless block_given?
300
+ if open?
301
+ yield @http_session
302
+ else
303
+ begin
304
+ open
305
+ yield @http_session
306
+ ensure
307
+ close
308
+ end
309
+ end
310
+ end
311
+
312
+
313
+ # Raises an error given a response. The Akismet documentation states that
314
+ # the HTTP headers of the response may contain error strings. I can't
315
+ # seem to find them, so for now just raise an unknown error.
316
+ # @param [Net::HTTPResponse] response
317
+ #
318
+ def raise_with_response( response )
319
+ raise Error.new( Error::UNKNOWN, 'Unknown error' )
320
+ end
321
+
322
+
323
+ # @param [String] method_name
324
+ # @param [String] user_ip
325
+ # @param [String] user_agent
326
+ # @param [Hash] params
327
+ # @return [Net::HTTPResponse]
328
+ # @raise [Akismet::Error]
329
+ # The API key is invalid.
330
+ #
331
+ def invoke_comment_method( method_name, user_ip, user_agent, params = {} )
332
+ params = params.merge :blog => home_url,
333
+ :user_ip => user_ip,
334
+ :user_agent => user_agent
335
+
336
+ response = in_http_session do |session|
337
+ invoke( session, method_name, params )
338
+ end
339
+
340
+ if response.body == 'invalid'
341
+ raise Error.new( Error::INVALID_API_KEY, 'Invalid API key' )
342
+ raise Error, 'Invalid API key'
343
+ end
344
+
345
+ response
346
+ end
347
+
348
+
349
+ # @param [Net::HTTP] http_session
350
+ # A started HTTP session
351
+ # @param [String] method_name
352
+ # @return [Net::HTTPResponse]
353
+ # @raise [Akismet::Error]
354
+ # An HTTP response other than 200 is received.
355
+ #
356
+ def invoke( http_session, method_name, params = {} )
357
+ response = http_session.post( "/1.1/#{ method_name }",
358
+ url_encode( params ),
359
+ http_headers )
360
+
361
+ unless response.is_a?( Net::HTTPOK )
362
+ raise Error, "HTTP #{ response.code } received (expected 200)"
363
+ end
364
+
365
+ response
366
+ end
367
+
368
+
369
+ # @return [Hash]
370
+ #
371
+ def http_headers
372
+ { 'User-Agent' => user_agent }
373
+ end
374
+
375
+
376
+ # @return [String]
377
+ #
378
+ def url_encode( hash = {} )
379
+ hash.collect do |k, v|
380
+ "#{ CGI.escape( k.to_s ) }=#{ CGI.escape( v.to_s ) }"
381
+ end.join( "&" )
382
+ end
383
+
384
+
385
+ # From the Akismet documentation:
386
+ # If possible, your user agent string should always use the following
387
+ # format: Application Name/Version | Plugin Name/Version
388
+ # @return [String]
389
+ #
390
+ def user_agent
391
+ [ user_agent_app, user_agent_plugin ].compact.join( " | " )
38
392
  end
39
393
 
394
+
395
+ # Returns nil if the Client was instantiated without an app_name.
396
+ # @return [String]
397
+ #
398
+ def user_agent_app
399
+ app_name && [ app_name, app_version ].compact.join( "/" )
400
+ end
401
+
402
+
403
+ # @return [String]
404
+ #
405
+ def user_agent_plugin
406
+ "Ruby Akismet/#{ Akismet::VERSION }"
407
+ end
408
+
40
409
  end
41
- end
410
+ end
@@ -0,0 +1,23 @@
1
+ module Akismet
2
+
3
+ class Error < StandardError
4
+
5
+ UNKNOWN = 1
6
+ INVALID_API_KEY = 2
7
+
8
+ # An error code corresponding to a constant in Akismet::Error.
9
+ # @return [Integer]
10
+ attr_reader :code
11
+
12
+ # @param [String] message
13
+ # A human-readable description of the error.
14
+ # @param [Integer] code
15
+ # An error code corresponding to a constant in Akismet::Error.
16
+ #
17
+ def initialize( code, message = nil )
18
+ super( message )
19
+ @code = code
20
+ end
21
+ end
22
+
23
+ end
@@ -1,3 +1,7 @@
1
1
  module Akismet
2
- VERSION = '0.1.2'
2
+ # The version of the Akismet gem.
3
+ VERSION = '1.0.0'
4
+
5
+ # The supported version of the Akismet API.
6
+ API_VERSION = '1.3'
3
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: akismet
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
5
- prerelease: false
4
+ hash: 23
5
+ prerelease:
6
6
  segments:
7
- - 0
8
7
  - 1
9
- - 2
10
- version: 0.1.2
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jonah Burke
@@ -15,13 +15,11 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-01 00:00:00 -05:00
19
- default_executable:
18
+ date: 2012-01-14 00:00:00 Z
20
19
  dependencies: []
21
20
 
22
21
  description: A Ruby client for the Akismet API
23
- email:
24
- - jonah@bigthink.com
22
+ email: jonah@jonahb.com
25
23
  executables: []
26
24
 
27
25
  extensions: []
@@ -29,14 +27,15 @@ extensions: []
29
27
  extra_rdoc_files: []
30
28
 
31
29
  files:
30
+ - README.md
31
+ - MIT-LICENSE
32
32
  - lib/akismet/client.rb
33
- - lib/akismet/service.rb
33
+ - lib/akismet/error.rb
34
34
  - lib/akismet/version.rb
35
35
  - lib/akismet.rb
36
- has_rdoc: true
37
- homepage: http://github.com/bigthink/akismet
38
- licenses: []
39
-
36
+ homepage: http://github.com/jonahb/akismet
37
+ licenses:
38
+ - MIT
40
39
  post_install_message:
41
40
  rdoc_options: []
42
41
 
@@ -45,31 +44,28 @@ require_paths:
45
44
  required_ruby_version: !ruby/object:Gem::Requirement
46
45
  none: false
47
46
  requirements:
48
- - - ~>
47
+ - - ">="
49
48
  - !ruby/object:Gem::Version
50
- hash: 59
49
+ hash: 3
51
50
  segments:
52
- - 1
53
- - 8
54
- - 6
55
- version: 1.8.6
51
+ - 0
52
+ version: "0"
56
53
  required_rubygems_version: !ruby/object:Gem::Requirement
57
54
  none: false
58
55
  requirements:
59
56
  - - ">="
60
57
  - !ruby/object:Gem::Version
61
- hash: 17
58
+ hash: 3
62
59
  segments:
63
- - 1
64
- - 3
65
- - 5
66
- version: 1.3.5
60
+ - 0
61
+ version: "0"
67
62
  requirements: []
68
63
 
69
64
  rubyforge_project:
70
- rubygems_version: 1.3.7
65
+ rubygems_version: 1.8.12
71
66
  signing_key:
72
67
  specification_version: 3
73
68
  summary: A Ruby client for the Akismet API
74
69
  test_files: []
75
70
 
71
+ has_rdoc: yard
@@ -1,87 +0,0 @@
1
- require 'net/http'
2
- require 'cgi'
3
-
4
- module Akismet
5
-
6
- AKISMET_VERSION = 1.1
7
- PORT = 80
8
- HEADERS = { 'User-Agent' => "Ruby/Akismet #{ VERSION }" }
9
-
10
- module CommentType
11
- COMMENT = 'comment'
12
- TRACKBACK = 'trackback'
13
- PINGBACK = 'pingback'
14
- end
15
-
16
- # Parameters to +comment_check+, +submit_ham+, and +submit_spam+ may
17
- # include:
18
- #
19
- # * content_type
20
- # * comment_author
21
- # * comment_author_email
22
- # * comment_author_url
23
- # * comment_content
24
- # * others representing HTTP headers and server environment vars
25
- #
26
- # See http://akismet.com/development/api/#comment-check for more info.
27
- #
28
- class Service
29
-
30
- def verify_key( api_key, home_url )
31
- response = invoke( 'verify-key', 'rest.akismet.com', :blog => home_url,
32
- :key => api_key )
33
-
34
- response.is_a?( Net::HTTPOK ) && response.body == 'valid'
35
- end
36
-
37
- def comment_check( api_key, home_url, user_ip, user_agent, params = {} )
38
- response = invoke_comment_method 'comment-check', api_key, home_url,
39
- user_ip, user_agent, params
40
-
41
- response.is_a?( Net::HTTPOK ) && response.body == 'true'
42
- end
43
-
44
- def submit_ham( api_key, home_url, user_ip, user_agent, params = {} )
45
- invoke_comment_method 'submit-ham', api_key, home_url, user_ip,
46
- user_agent, params
47
-
48
- nil
49
- end
50
-
51
- def submit_spam( api_key, home_url, user_ip, user_agent, params = {} )
52
- invoke_comment_method 'submit-spam', api_key, home_url, user_ip,
53
- user_agent, params
54
-
55
- nil
56
- end
57
-
58
- private
59
-
60
- def invoke_comment_method( method_name, api_key, home_url, user_ip, user_agent, params = {} )
61
- host = "#{ api_key }.rest.akismet.com"
62
-
63
- params = params.merge :blog => home_url,
64
- :user_ip => user_ip,
65
- :user_agent => user_agent
66
-
67
- invoke method_name, host, params
68
- end
69
-
70
- # returns an HTTP response
71
- def invoke( method_name, host, params )
72
- path = "/#{ AKISMET_VERSION }/#{ method_name }"
73
- data = url_encode( params )
74
-
75
- Net::HTTP.start( host, PORT ) do |http|
76
- http.post( path, data, HEADERS )
77
- end
78
- end
79
-
80
- def url_encode( hash = {} )
81
- hash.collect do |k, v|
82
- "#{ CGI.escape( k.to_s ) }=#{ CGI.escape( v.to_s ) }"
83
- end.join( "&" )
84
- end
85
-
86
- end
87
- end