love 0.0.4 → 0.0.5

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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- love (0.0.4)
4
+ love (0.0.5)
5
5
  activesupport
6
6
  yajl-ruby
7
7
 
@@ -22,12 +22,12 @@ I previously used HTTParty to connect to the Tender API, but I ran into two issu
22
22
 
23
23
  == Installation
24
24
 
25
- Run <tt>gem install love</tt> or add <tt>gem 'love'</tt> to your Gemfile.
25
+ Run <tt>gem install love</tt> or add <tt>gem "love"</tt> to your Gemfile.
26
26
 
27
27
  == Usage
28
28
 
29
29
  require 'love'
30
- tender = Love.connect('account', 'api_key')
30
+ tender = Love.connect('site', 'api_key')
31
31
 
32
32
  # Loop over all discussions:
33
33
  tender.each_discussion do |discussion|
@@ -35,11 +35,15 @@ Run <tt>gem install love</tt> or add <tt>gem 'love'</tt> to your Gemfile.
35
35
  end
36
36
 
37
37
  # Also available:
38
- tender.each_user { |c| ... }
38
+ tender.each_user { |u| ... }
39
39
  tender.each_queue { |q| ... }
40
40
  tender.each_category { |c| ... }
41
41
 
42
+ # Or get a single record, using an ID or HREF:
43
+ d = tender.get_discussion('12345')
44
+ u = tender.get_user('https://api.tenderapp.com/site/users/12345')
45
+
42
46
  == About
43
47
 
44
- This library is Written by Willem van Bergen for Shopify, and is MIT licensed.
48
+ This library is written by Willem van Bergen for Shopify, and is MIT licensed.
45
49
 
@@ -1,143 +1,313 @@
1
1
  require 'uri'
2
+ require 'cgi'
2
3
  require 'net/https'
3
- require 'active_support/core_ext/module/attribute_accessors.rb'
4
+ require 'active_support/core_ext/module/attribute_accessors'
5
+ require 'active_support/core_ext/hash/keys'
4
6
  require 'yajl'
5
7
 
8
+ # Love is a small Ruby library to interact with the Tender REST API.
9
+ # The main object to work with is {Love::Client}, which is returned
10
+ # by calling {Love.connect}.
11
+ #
12
+ # It is dedicated to the awesome work Aaeron Patterson has been giving
13
+ # to the Ruby community.
6
14
  module Love
7
15
 
8
- VERSION = "0.0.4"
16
+ # The current gem version. Will be updated automatically by the gem
17
+ # release script.
18
+ VERSION = "0.0.5"
9
19
 
10
- # Create a custom exception class.
20
+ # Exception class a custom exception class.
11
21
  class Exception < StandardError; end
12
22
 
13
- # Class for unauthorized exceptions
23
+ # Exception class for unauthorized requests or resources that are forbidden
14
24
  class Unauthorized < Love::Exception; end
15
25
 
26
+ # Exception class for resources that are not found
27
+ class NotFound < Love::Exception; end
16
28
 
17
- def self.connect(account, api_key, options = {})
18
- Love::Client.new(account, api_key, options)
29
+
30
+ # Connects to the Tender API to access a site using an API key.
31
+ #
32
+ # This method doesn't do any communication yet with the Tender API, it will
33
+ # only set up the client with the provided credentials and options.
34
+ #
35
+ # By default, the client will use a new connection for every request. It can also
36
+ # open a persistent connection if you want to do multiple requests, by passing
37
+ # <tt>:persistent => true</tt> as option. In this case, you need to call {Love::Client#close_connection}
38
+ # when you are done with your session. You can also provide a block to this method
39
+ # which will automatically open up a persistent connection and close it at the end
40
+ # of the block.
41
+ #
42
+ # @param (see Love::Client#initialize)
43
+ # @option (see Love::Client#initialize)
44
+ # @overload connect(site, api_key, options = {}) { ... }
45
+ # @yield [Love::Client] An API client to work with using a persistent connection.
46
+ # @return The return value of the block. The persistent connection will be closed
47
+ # @overload connect(site, api_key, options = {})
48
+ # @return [Love::Client] An API client to work with. By default, the returned
49
+ # client will not use a persistent TCP connection, so a new connection will
50
+ # be
51
+ # @see Love::Client#initialize
52
+ def self.connect(site, api_key, options = {}, &block)
53
+ if block_given?
54
+ begin
55
+ client = Love::Client.new(site, api_key, options.merge(:persistent => true))
56
+ block.call(client)
57
+ ensure
58
+ client.close_connection
59
+ end
60
+ else
61
+ Love::Client.new(site, api_key, options)
62
+ end
19
63
  end
20
64
 
65
+ # @return [Logger] Set this attribute to a Logger instance to log the
66
+ # HTTP connectivity somewhere.
21
67
  mattr_accessor :logger
22
68
 
69
+ # Module to work with Tender REST URIs.
23
70
  module ResourceURI
71
+
72
+ # Returns a collection URI, based on an URI instance, a complete URI string or just a resource name.
73
+ # @return [URI] The URI on which the REST resource collection is accessible through the Tender REST API.
74
+ # @raise [Love::Exception] If the input cannot be converted into a resource collection URI.
24
75
  def collection_uri(input)
25
76
  case input.to_s
26
77
  when /^[\w-]+$/
27
- ::URI.parse("https://api.tenderapp.com/#{account}/#{input}")
28
- when %r[^https?://api\.tenderapp\.com/#{account}/[\w-]+]
78
+ ::URI.parse("https://api.tenderapp.com/#{site}/#{input}")
79
+ when %r[^https?://api\.tenderapp\.com/#{site}/[\w-]+]
29
80
  ::URI.parse(input.to_s)
30
81
  else
31
82
  raise Love::Exception, "This does not appear to be a valid Tender category URI!"
32
83
  end
33
84
  end
34
-
85
+
86
+ # Returns a resource URI, based on an URI instance, a complete URI string or just a resource ID.
87
+ # @param [Object input The complete URI or just resource ID as URI, String or Integer.
88
+ # @param [String] kind The kind of resource.
89
+ # @return [URI] The URI on which the REST resource is accessible through the Tender REST API.
90
+ # @raise [Love::Exception] If the input cannot be converted into a resource URI.
35
91
  def singleton_uri(input, kind)
36
92
  case input.to_s
37
93
  when /^\d+/
38
- ::URI.parse("https://api.tenderapp.com/#{account}/#{kind}/#{input}")
39
- when %r[^https?://api\.tenderapp\.com/#{account}/#{kind}/\d+]
94
+ ::URI.parse("https://api.tenderapp.com/#{site}/#{kind}/#{input}")
95
+ when %r[^https?://api\.tenderapp\.com/#{site}/#{kind}/\d+]
40
96
  ::URI.parse(input.to_s)
41
97
  else
42
98
  raise Love::Exception, "This does not appear to be a Tender #{kind} URI or ID!"
43
99
  end
44
100
  end
45
101
 
46
- def request_uri(base_uri, added_params = {})
102
+ # Appends GET parameters to a URI instance. Duplicate parameters will
103
+ # be replaced with the new value.
104
+ # @param [URI] base_uri The original URI to work with (will not be modified)
105
+ # @param [Hash] added_params To GET params to add.
106
+ # @return [URI] The URI with appended GET parameters
107
+ def append_query(base_uri, added_params = {})
47
108
  base_params = base_uri.query ? CGI.parse(base_uri.query) : {}
48
- get_params = base_params.merge(added_params || {})
109
+ get_params = base_params.merge(added_params.stringify_keys)
49
110
  base_uri.dup.tap do |uri|
50
- uri.query = get_params.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join('&')
111
+ assignments = get_params.map do |k, v|
112
+ case v
113
+ when Array; v.map { |val| "#{::CGI.escape(k.to_s)}=#{::CGI.escape(val.to_s)}" }.join('&')
114
+ else "#{::CGI.escape(k.to_s)}=#{::CGI.escape(v.to_s)}"
115
+ end
116
+ end
117
+ uri.query = assignments.join('&')
51
118
  end
52
119
  end
53
120
  end
54
-
121
+
122
+ # The Love::Client class acts as a client to the Tender REST API. Obtain an instance of this
123
+ # class by calling {Love.connect} instead of instantiating this class directly.
124
+ #
125
+ # You can either fetch individual resources using {#get_user}, {#get_discussion}, and
126
+ # similar methods, or iterate over collections using {#each_discussion}, {#each_category}
127
+ # and similar methods.
55
128
  class Client
56
129
 
130
+ # The Tender API host to connect to.
131
+ TENDER_API_HOST = 'api.tenderapp.com'
132
+
57
133
  include Love::ResourceURI
58
134
 
59
- attr_reader :account
135
+ # @return [String] The site to work with
136
+ attr_reader :site
137
+
138
+ # @return [String] The API key to authenticate with.
60
139
  attr_reader :api_key
61
140
 
141
+ # @return [Float] The number of seconds to sleep between paged requests.
62
142
  attr_accessor :sleep_between_requests
63
143
 
64
- def initialize(account, api_key, options = {})
65
- @account, @api_key = account, api_key
144
+ # Initializes the client.
145
+ # @param [String] site The site to work with.
146
+ # @param [String] api_key The API key for this site.
147
+ # @param [Hash] options Connectivity options.
148
+ # @option options [Boolean] :persistent (false) Whether to create a persistent TCP connection.
149
+ # @option options [Float] :sleep_between_requests (0.5) The time between requests in seconds.
150
+ # @see Love.connect
151
+ def initialize(site, api_key, options = {})
152
+ @site, @api_key = site, api_key
66
153
 
67
154
  # Handle options
155
+ @persistent = !!options[:persistent]
68
156
  @sleep_between_requests = options[:sleep_between_requests] || 0.5
69
157
  end
70
158
 
71
- def get_user(id_or_href, options = {})
159
+ # Returns a single Tender user.
160
+ # @param [URI, String, Integer] id_or_href The user ID or HREF. Can be either a URI
161
+ # instance, a string containing a URI, or a user ID as a numeric string or integer.
162
+ # @return [Hash] The user attributes in a Hash.
163
+ def get_user(id_or_href)
72
164
  get(singleton_uri(id_or_href, 'users'))
73
165
  end
74
-
75
- def get_discussion(id_or_href, options = {})
166
+
167
+ # Returns a single Tender discussion.
168
+ # @param [URI, String, Integer] id_or_href The discussion ID or HREF. Can be either a URI
169
+ # instance, a string containing a URI, or a discussion ID as a numeric string or integer.
170
+ # @return [Hash] The discussion attributes in a Hash.
171
+ def get_discussion(id_or_href)
76
172
  get(singleton_uri(id_or_href, 'discussions'))
77
173
  end
78
174
 
79
- def get_category(id_or_href, options = {})
175
+ # Returns a single Tender category.
176
+ # @param [URI, String, Integer] id_or_href The category ID or HREF. Can be either a URI
177
+ # instance, a string containing a URI, or a category ID as a numeric string or integer.
178
+ # @return [Hash] The category attributes in a Hash.
179
+ def get_category(id_or_href)
80
180
  get(singleton_uri(id_or_href, 'categories'))
81
181
  end
82
182
 
83
- def get_queue(id_or_href, options = {})
183
+ # Returns a single Tender queue.
184
+ # @param [URI, String, Integer] id_or_href The queue ID or HREF. Can be either a URI
185
+ # instance, a string containing a URI, or a queue ID as a numeric string or integer.
186
+ # @return [Hash] The queue attributes in a Hash.
187
+ def get_queue(id_or_href)
84
188
  get(singleton_uri(id_or_href, 'queues'), options)
85
189
  end
86
-
190
+
191
+ # Iterates over all Tender categories.
192
+ # @yield [Hash] The attributes of each category will be yielded as (nested) Hash.
193
+ # @option (see #paged_each)
194
+ # @return [nil]
87
195
  def each_category(options = {}, &block)
88
- buffered_each(collection_uri('categories'), 'categories', options, &block)
196
+ paged_each(collection_uri('categories'), 'categories', options, &block)
89
197
  end
90
198
 
199
+ # Iterates over all Tender users.
200
+ # @yield [Hash] The attributes of each user will be yielded as (nested) Hash.
201
+ # @option (see #paged_each)
202
+ # @return [nil]
91
203
  def each_queue(options = {}, &block)
92
- buffered_each(collection_uri('queues'), 'named_queues', options, &block)
204
+ paged_each(collection_uri('queues'), 'named_queues', options, &block)
93
205
  end
94
206
 
207
+ # Iterates over all Tender users.
208
+ # @yield [Hash] The attributes of each user will be yielded as (nested) Hash.
209
+ # @option (see #paged_each)
210
+ # @return [nil]
95
211
  def each_user(options = {}, &block)
96
- buffered_each(collection_uri('users'), 'users', options, &block)
212
+ paged_each(collection_uri('users'), 'users', options, &block)
97
213
  end
98
214
 
215
+ # Iterates over all Tender discussions.
216
+ # @yield [Hash] The attributes of each discussion will be yielded as (nested) Hash.
217
+ # @option (see #paged_each)
218
+ # @return [nil]
99
219
  def each_discussion(options = {}, &block)
100
- buffered_each(collection_uri('discussions'), 'discussions', options, &block)
220
+ paged_each(collection_uri('discussions'), 'discussions', options, &block)
221
+ end
222
+
223
+ # Returns a persistent connection to the server, reusing a connection of it was
224
+ # previously established.
225
+ #
226
+ # This method is mainly used for internal use but can be used to do advanced
227
+ # HTTP connectivity with the Tender API server.
228
+ #
229
+ # @return [Net::HTTP] The net/http connection instance.
230
+ def connection
231
+ @connection ||= Net::HTTP.new(TENDER_API_HOST, Net::HTTP.https_default_port).tap do |http|
232
+ http.use_ssl = true
233
+ # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
234
+ http.start
235
+ end
236
+ end
237
+
238
+ # Closes the persistent connectio to the server
239
+ # @return [nil]
240
+ def close_connection
241
+ @connection.finish if connected?
242
+ end
243
+
244
+ # @return [Boolean] <tt>true</tt> iff the client currently has a TCP connection with the Tender API server.
245
+ def connected?
246
+ @connection && @connection.started?
247
+ end
248
+
249
+ # @return [Boolean] <tt>true</tt> iff the client is using persistent connections.
250
+ def persistent?
251
+ @persistent
101
252
  end
102
253
 
103
254
  protected
255
+
256
+ def request_headers
257
+ @request_headers ||= { "Accept" => "application/vnd.tender-v1+json", "X-Tender-Auth" => api_key }
258
+ end
104
259
 
105
260
  def get(uri)
106
- Love.logger.debug "GET #{uri}" if Love.logger
261
+ raise Love::Exception, "This is not a Tender API URI." unless uri.host = TENDER_API_HOST
107
262
 
108
- http = Net::HTTP.new(uri.host, uri.port)
109
- if uri.scheme = 'https'
110
- http.use_ssl = true
111
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
112
- end
263
+ Love.logger.debug "GET #{uri.request_uri}" if Love.logger
113
264
 
114
- req = Net::HTTP::Get.new(uri.request_uri, {
115
- "Accept" => "application/vnd.tender-v1+json",
116
- "X-Tender-Auth" => api_key
117
- })
118
-
119
- response = http.request(req)
120
- case response.code
121
- when /^2\d\d/
265
+ request = Net::HTTP::Get.new(uri.request_uri, request_headers)
266
+ response = connection.request(request)
267
+ case response
268
+ when Net::HTTPSuccess; Yajl::Parser.new.parse(safely_convert_to_utf8(response.body))
269
+ when Net::HTTPUnauthorized; raise Love::Unauthorized, "Invalid credentials used!"
270
+ when Net::HTTPForbidden; raise Love::Unauthorized, "You don't have permission to access this resource!"
271
+ when Net::NotFound; raise Love::NotFound, "The resource #{uri} was not found!"
272
+ else raise Love::Exception, "#{response.code}: #{response.body}"
273
+ end
274
+ ensure
275
+ close_connection unless persistent?
276
+ end
277
+
278
+ # Converts a binary, (alomst) UTF-8 string into an actual UTF-8 string.
279
+ # It will replace any unknown characters or unvalid byte sequences into a UTF-8
280
+ # "unknown character" question mark.
281
+ #
282
+ # @param [String] binary_string The input string, should have binary encoding
283
+ # @return [String] The string using UTF-8 encoding.
284
+ def safely_convert_to_utf8(binary_string)
285
+ if binary_string.respond_to?(:force_encoding)
286
+ # Ruby 1.9
122
287
  converter = Encoding::Converter.new('binary', 'utf-8', :invalid => :replace, :undef => :replace)
123
- Yajl::Parser.new.parse(converter.convert(response.body))
124
- when '401'
125
- raise Love::Unauthorized, "Invalid credentials used!"
288
+ converter.convert(binary_string)
126
289
  else
127
- raise Love::Exception, "#{response.code}: #{response.body}"
290
+ # Ruby 1.8 - currently don't do anything
291
+ binary_string
128
292
  end
129
293
  end
130
294
 
131
- def buffered_each(uri, list_key, options = {}, &block)
295
+ # Iterates over a collection, issuing multiple requests to get all the paged results.
296
+ #
297
+ # @option options [Date] :since Only include records updated since the provided date.
298
+ # Caution: not supported by all resources.
299
+ # @option options [Integer] :start_page The initial page number to request.
300
+ # @option options [Integer] :end_page The final page number to request.
301
+ def paged_each(uri, list_key, options = {}, &block)
132
302
  query_params = {}
133
303
  query_params[:since] = options[:since].to_date.to_s(:db) if options[:since]
134
304
  query_params[:page] = [options[:start_page].to_i, 1].max rescue 1
135
305
 
136
- initial_result = get(request_uri(uri, query_params))
306
+ initial_result = get(append_query(uri, query_params))
137
307
 
138
308
  # Determine the amount of pages that is going to be requested.
139
- max_page = (initial_result['total'].to_f / initial_result['per_page'].to_f).ceil
140
- end_page = options[:end_page].nil? ? max_page : [options[:end_page].to_i, max_page].min
309
+ max_page = (initial_result['total'].to_f / initial_result['per_page'].to_f).ceil
310
+ end_page = options[:end_page].nil? ? max_page : [options[:end_page].to_i, max_page].min
141
311
 
142
312
  # Print out some initial debugging information
143
313
  Love.logger.debug "Paged requests to #{uri}: #{max_page} total pages, importing #{query_params[:page]} upto #{end_page}." if Love.logger
@@ -151,7 +321,7 @@ module Love
151
321
  start_page = query_params[:page].to_i + 1
152
322
  start_page.upto(end_page) do |page|
153
323
  query_params[:page] = page
154
- result = get(request_uri(uri, query_params))
324
+ result = get(append_query(uri, query_params))
155
325
  if result[list_key].kind_of?(Array)
156
326
  result[list_key].each { |record| yield(record) }
157
327
  sleep(sleep_between_requests) if sleep_between_requests
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
3
3
 
4
4
  # Do not change the version and date fields by hand. This will be done
5
5
  # automatically by the gem release script.
6
- s.version = "0.0.4"
7
- s.date = "2010-11-29"
6
+ s.version = "0.0.5"
7
+ s.date = "2010-11-30"
8
8
 
9
9
  s.summary = "Ruby library to access the Tender REST API."
10
10
  s.description = <<-EOT
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Love::ResourceURI do
4
4
 
5
5
  include Love::ResourceURI
6
- def account; "mysupport"; end
6
+ def site; "mysupport"; end
7
7
 
8
8
  describe '#collection_uri' do
9
9
 
@@ -23,7 +23,7 @@ describe Love::ResourceURI do
23
23
  collection_uri(uri).should == uri
24
24
  end
25
25
 
26
- it "should not accept a URI for another account" do
26
+ it "should not accept a URI for another site" do
27
27
  lambda { collection_uri('https://api.tenderapp.com/other/bars') }.should raise_error(Love::Exception)
28
28
  end
29
29
 
@@ -52,9 +52,9 @@ describe Love::ResourceURI do
52
52
  uri = URI.parse('https://api.tenderapp.com/mysupport/bars/789')
53
53
  singleton_uri(uri, 'bars').should be_kind_of(::URI)
54
54
  singleton_uri(uri, 'bars').should == uri
55
- end
55
+ end
56
56
 
57
- it "should not accept a URI for another account" do
57
+ it "should not accept a URI for another site" do
58
58
  lambda { singleton_uri('https://api.tenderapp.com/other/bars/123', 'bars') }.should raise_error(Love::Exception)
59
59
  end
60
60
 
@@ -64,7 +64,32 @@ describe Love::ResourceURI do
64
64
 
65
65
  it "should not weird resource IDs" do
66
66
  lambda { singleton_uri('%!&', 'bars') }.should raise_error(Love::Exception)
67
- end
67
+ end
68
+ end
69
+
70
+ describe '#append_query' do
71
+ before { @uri = URI.parse('https://api.tenderapp.com/') }
72
+
73
+ it "should return a URI instance" do
74
+ append_query(@uri, :a => 'b').should be_kind_of(URI)
75
+ end
76
+
77
+ it "should add a correctly quoted query string" do
78
+ adjusted = append_query(@uri, :a => 'some data')
79
+ adjusted.to_s.should == 'https://api.tenderapp.com/?a=some+data'
80
+ end
81
+
82
+ it "should keep existing query parameters intact" do
83
+ @uri.query = 'foo=bar'
84
+ adjusted = append_query(@uri, :a => 'some data')
85
+ adjusted.to_s.should == 'https://api.tenderapp.com/?foo=bar&a=some+data'
86
+ end
87
+
88
+ it "should overwrite existing parameters" do
89
+ @uri.query = 'foo=bar'
90
+ adjusted = append_query(@uri, :foo => 'baz')
91
+ adjusted.to_s.should == 'https://api.tenderapp.com/?foo=baz'
92
+ end
68
93
  end
69
94
  end
70
95
 
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: love
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
5
4
  prerelease: false
6
5
  segments:
7
6
  - 0
8
7
  - 0
9
- - 4
10
- version: 0.0.4
8
+ - 5
9
+ version: 0.0.5
11
10
  platform: ruby
12
11
  authors:
13
12
  - Willem van Bergen
@@ -15,64 +14,60 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2010-11-29 00:00:00 -05:00
17
+ date: 2010-11-30 00:00:00 -05:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
22
  requirement: &id001 !ruby/object:Gem::Requirement
23
23
  none: false
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- hash: 3
28
27
  segments:
29
28
  - 0
30
29
  version: "0"
31
- name: activesupport
32
- prerelease: false
33
30
  type: :runtime
31
+ prerelease: false
34
32
  version_requirements: *id001
35
33
  - !ruby/object:Gem::Dependency
34
+ name: yajl-ruby
36
35
  requirement: &id002 !ruby/object:Gem::Requirement
37
36
  none: false
38
37
  requirements:
39
38
  - - ">="
40
39
  - !ruby/object:Gem::Version
41
- hash: 3
42
40
  segments:
43
41
  - 0
44
42
  version: "0"
45
- name: yajl-ruby
46
- prerelease: false
47
43
  type: :runtime
44
+ prerelease: false
48
45
  version_requirements: *id002
49
46
  - !ruby/object:Gem::Dependency
47
+ name: rake
50
48
  requirement: &id003 !ruby/object:Gem::Requirement
51
49
  none: false
52
50
  requirements:
53
51
  - - ">="
54
52
  - !ruby/object:Gem::Version
55
- hash: 3
56
53
  segments:
57
54
  - 0
58
55
  version: "0"
59
- name: rake
60
- prerelease: false
61
56
  type: :development
57
+ prerelease: false
62
58
  version_requirements: *id003
63
59
  - !ruby/object:Gem::Dependency
60
+ name: rspec
64
61
  requirement: &id004 !ruby/object:Gem::Requirement
65
62
  none: false
66
63
  requirements:
67
64
  - - ~>
68
65
  - !ruby/object:Gem::Version
69
- hash: 7
70
66
  segments:
71
67
  - 2
72
68
  version: "2"
73
- name: rspec
74
- prerelease: false
75
69
  type: :development
70
+ prerelease: false
76
71
  version_requirements: *id004
77
72
  description: " A simple API wrapper for Tender, that handles paged results, uses yajl-ruby for\n JSON parsing, and manually handles UTF-8 encoding to circumvent the invalid UTF-8\n character problem in Ruby 1.9.\n"
78
73
  email:
@@ -114,7 +109,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
114
109
  requirements:
115
110
  - - ">="
116
111
  - !ruby/object:Gem::Version
117
- hash: 3
112
+ hash: 4412278213524451879
118
113
  segments:
119
114
  - 0
120
115
  version: "0"
@@ -123,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
118
  requirements:
124
119
  - - ">="
125
120
  - !ruby/object:Gem::Version
126
- hash: 3
121
+ hash: 4412278213524451879
127
122
  segments:
128
123
  - 0
129
124
  version: "0"