rets 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2ed1e7653c004159b6f9042dc917f9c8a30be926
4
- data.tar.gz: 4b2275a656b7240b5d94a4d0f9c0ad5a62a18eeb
3
+ metadata.gz: 4f011e9a58934f4b5152a5f01428b58275602342
4
+ data.tar.gz: 188cdccdacaeb7b8c64df570208f073bfb00d9fb
5
5
  SHA512:
6
- metadata.gz: 3853c2929ab43105aba05ceebe185e4c3b86271bdc99b8ec42e9578921ee9d2740c98cfea2b31a13e22de6e3d5e4fa608f61399133593c43dae14f3c63ffc772
7
- data.tar.gz: 7d177e112e61b54dc347811bdfa6a44aa2dd472ce45b061dbf180e636b4e5f2c4318e6d8ea08dc5c1013615118eefc4da401fa4b7bd3da629317d7022221f30b
6
+ metadata.gz: 9ce589fca7e82987db2bdf0eb3432e9482bf1344b9d79a69d56b00f29219dd1495680c903bab456e8759e319ec6952245bb3bb88d69cbd1dcf4124a3abf8957e
7
+ data.tar.gz: 80dbdc930daa3eedb1f9566b7decbb4b69964396f16300522d481c226b442ff71d5bbbcf4d0c8049ee6a3e0df88fd0f490e6b69d11041265ca9ec94d069de964
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
- ### 0.6.0 / 2013-10-30
1
+ ### 0.7.0 / 2015-01-16
2
+
3
+ * feature: optionally treat No Records Found as not an error
4
+ * fix: update httpclient version, patches SSL vulnerabilities
5
+ * feature: work around bogus http status codes that don't agree with XML body
6
+
7
+ ### 0.6.0 / 2014-11-26
2
8
 
3
9
  * fix: fix spelling error that created misleading exceptions
4
10
  * feature: track stats for http requests sent
data/README.md CHANGED
@@ -11,12 +11,20 @@ A pure-ruby library for fetching data from [RETS] servers.
11
11
 
12
12
  ## REQUIREMENTS:
13
13
 
14
- * [net-http-persistent]
14
+ * [httpclient]
15
15
  * [nokogiri]
16
16
 
17
- [net-http-persistent]: http://seattlerb.rubyforge.org/net-http-persistent/
17
+ [httpclient]: https://github.com/nahi/httpclient
18
18
  [nokogiri]: http://nokogiri.org
19
19
 
20
+ ## INSTALLATION:
21
+ ```
22
+ gem install rets
23
+
24
+ # or add it to your Gemfile if using Bundler then run bundle install
25
+ gem 'rets'
26
+ ```
27
+
20
28
  ## EXAMPLE USAGE:
21
29
 
22
30
  We need work in this area! There are currently a few guideline examples in the `example` folder on connecting, fetching a property's data, and fetching a property's photos.
data/Rakefile CHANGED
@@ -9,12 +9,12 @@ Hoe.plugin :gemspec
9
9
  Hoe.spec 'rets' do
10
10
  developer 'Estately, Inc. Open Source', 'opensource@estately.com'
11
11
 
12
- extra_deps << [ "httpclient", "~> 2.3.0" ]
13
- extra_deps << [ "nokogiri", "~> 1.5.2" ]
12
+ extra_deps << [ "httpclient", "~> 2.4" ]
13
+ extra_deps << [ "nokogiri", "~> 1.5" ]
14
14
 
15
- extra_dev_deps << [ "mocha", "~> 0.11.0" ]
16
- extra_dev_deps << [ "vcr", "~> 2.2.2" ]
17
- extra_dev_deps << [ "webmock", "~> 1.8.0" ]
15
+ extra_dev_deps << [ "mocha", "~> 0.11" ]
16
+ extra_dev_deps << [ "vcr", "~> 2.2" ]
17
+ extra_dev_deps << [ "webmock", "~> 1.8" ]
18
18
 
19
19
  ### Use markdown for changelog and readme
20
20
  self.history_file = 'CHANGELOG.md'
data/lib/rets.rb CHANGED
@@ -3,7 +3,7 @@ require 'digest/md5'
3
3
  require 'nokogiri'
4
4
 
5
5
  module Rets
6
- VERSION = '0.6.0'
6
+ VERSION = '0.7.0'
7
7
 
8
8
  MalformedResponse = Class.new(ArgumentError)
9
9
  UnknownResponse = Class.new(ArgumentError)
@@ -18,6 +18,16 @@ module Rets
18
18
  end
19
19
  end
20
20
 
21
+ class NoRecordsFound < ArgumentError
22
+ ERROR_CODE = 20201
23
+ attr_reader :reply_text
24
+
25
+ def initialize(reply_text)
26
+ @reply_text = reply_text
27
+ super("Got error code #{ERROR_CODE} (#{reply_text})")
28
+ end
29
+ end
30
+
21
31
  class InvalidRequest < ArgumentError
22
32
  attr_reader :error_code, :reply_text
23
33
  def initialize(error_code, reply_text)
@@ -28,10 +38,11 @@ module Rets
28
38
  end
29
39
 
30
40
  class UnknownCapability < ArgumentError
31
- attr_reader :capability_name
32
- def initialize(capability_name)
41
+ attr_reader :capability_name, :capabilities
42
+ def initialize(capability_name, capabilities=[])
33
43
  @capability_name = capability_name
34
- super("unknown capability #{capability_name}")
44
+ @capabilities = capabilities
45
+ super("unknown capability #{capability_name}, available capabilities #{capabilities}")
35
46
  end
36
47
  end
37
48
  end
data/lib/rets/client.rb CHANGED
@@ -56,14 +56,13 @@ module Rets
56
56
  end
57
57
  end
58
58
 
59
- # Attempts to login by making an empty request to the URL
60
- # provided in initialize. Returns the capabilities that the
61
- # RETS server provides, per http://retsdoc.onconfluence.com/display/rets172/4.10+Capability+URL+List.
59
+ # Attempts to login by making an empty request to the URL provided in
60
+ # initialize. Returns the capabilities that the RETS server provides, per
61
+ # page 34 of http://www.realtor.org/retsorg.nsf/retsproto1.7d6.pdf#page=34
62
62
  def login
63
63
  res = http_get(login_url)
64
- unless res.status_code == 200
65
- raise UnknownResponse, "bad response to login, expected a 200, but got #{res.status_code}. Body was #{res.body}."
66
- end
64
+ Client::ErrorChecker.check(res)
65
+
67
66
  self.capabilities = extract_capabilities(Nokogiri.parse(res.body))
68
67
  raise UnknownResponse, "Cannot read rets server capabilities." unless @capabilities
69
68
  @capabilities
@@ -112,18 +111,33 @@ module Rets
112
111
  def find_with_retries(opts = {})
113
112
  retries = 0
114
113
  resolve = opts.delete(:resolve)
114
+ find_with_given_retry(retries, resolve, opts)
115
+ end
116
+
117
+ def find_with_given_retry(retries, resolve, opts)
115
118
  begin
116
119
  find_every(opts, resolve)
117
- rescue AuthorizationFailure, InvalidRequest => e
118
- if retries < opts.fetch(:max_retries, 3)
119
- retries += 1
120
- @client_progress.find_with_retries_failed_a_retry(e, retries)
121
- clean_setup
122
- retry
120
+ rescue NoRecordsFound => e
121
+ if opts.fetch(:no_records_not_an_error, false)
122
+ @client_progress.no_records_found
123
+ opts[:count] == COUNT.only ? 0 : []
123
124
  else
124
- @client_progress.find_with_retries_exceeded_retry_count(e)
125
- raise e
125
+ handle_find_failure(retries, resolve, opts, e)
126
126
  end
127
+ rescue AuthorizationFailure, InvalidRequest => e
128
+ handle_find_failure(retries, resolve, opts, e)
129
+ end
130
+ end
131
+
132
+ def handle_find_failure(retries, resolve, opts, e)
133
+ if retries < opts.fetch(:max_retries, 3)
134
+ retries += 1
135
+ @client_progress.find_with_retries_failed_a_retry(e, retries)
136
+ clean_setup
137
+ find_with_given_retry(retries, resolve, opts)
138
+ else
139
+ @client_progress.find_with_retries_exceeded_retry_count(e)
140
+ raise e
127
141
  end
128
142
  end
129
143
 
@@ -291,7 +305,7 @@ module Rets
291
305
  def capability_url(name)
292
306
  val = capabilities[name] || capabilities[name.downcase]
293
307
 
294
- raise UnknownCapability.new(name) unless val
308
+ raise UnknownCapability.new(name, capabilities.keys) unless val
295
309
 
296
310
  begin
297
311
  if val.downcase.match(/^https?:\/\//)
@@ -340,7 +354,7 @@ module Rets
340
354
 
341
355
  class FakeLogger < Logger
342
356
  def initialize
343
- super("/dev/null")
357
+ super(IO::NULL)
344
358
  end
345
359
  end
346
360
 
@@ -367,7 +381,9 @@ module Rets
367
381
  reply_text = (rets_element.attr("ReplyText") || rets_element.attr("replyText")).value
368
382
  reply_code = (rets_element.attr("ReplyCode") || rets_element.attr("replyCode")).value.to_i
369
383
 
370
- if reply_code.nonzero?
384
+ if reply_code == NoRecordsFound::ERROR_CODE
385
+ raise NoRecordsFound.new(reply_text)
386
+ elsif reply_code.nonzero?
371
387
  raise InvalidRequest.new(reply_code, reply_text)
372
388
  else
373
389
  return
@@ -28,6 +28,10 @@ module Rets
28
28
  @stats.count("#{@stats_prefix}find_with_retries_exceeded_retry_count")
29
29
  end
30
30
 
31
+ def no_records_found
32
+ @logger.info("Rets::Client: No Records Found")
33
+ end
34
+
31
35
  def could_not_resolve_find_metadata(key)
32
36
  @stats.count("#{@stats_prefix}could_not_resolve_find_metadata")
33
37
  @logger.warn "Rets::Client: Can't resolve find metadata for #{key.inspect}"
@@ -14,7 +14,7 @@ module Rets
14
14
  http.set_auth(url, options[:username], options[:password])
15
15
  headers = extra_headers.merge(rets_extra_headers)
16
16
  res = nil
17
- log_http_traffic("POST", url, params, headers) do
17
+ log_http_traffic("GET", url, params, headers) do
18
18
  res = http.get(url, params, headers)
19
19
  end
20
20
  Client::ErrorChecker.check(res)
@@ -2,7 +2,7 @@ module Rets
2
2
  module Metadata
3
3
  class TableFactory
4
4
  def self.build(table_fragment, resource)
5
- enum?(table_fragment) ? LookupTable.new(table_fragment, resource) : Table.new(table_fragment)
5
+ enum?(table_fragment) ? LookupTable.new(table_fragment, resource) : Table.new(table_fragment, resource)
6
6
  end
7
7
 
8
8
  def self.enum?(table_fragment)
@@ -18,9 +18,11 @@ module Rets
18
18
  attr_accessor :name
19
19
  attr_accessor :long_name
20
20
  attr_accessor :table_fragment
21
+ attr_accessor :resource
21
22
 
22
- def initialize(table_fragment)
23
+ def initialize(table_fragment, resource)
23
24
  self.table_fragment = table_fragment
25
+ self.resource = resource
24
26
  self.type = table_fragment["DataType"]
25
27
  self.name = table_fragment["SystemName"]
26
28
  self.long_name = table_fragment["LongName"]
@@ -28,6 +30,7 @@ module Rets
28
30
 
29
31
  def print_tree
30
32
  puts " Table: #{name}"
33
+ puts " Resource: #{resource.id}"
31
34
  puts " ShortName: #{ table_fragment["ShortName"] }"
32
35
  puts " LongName: #{ table_fragment["LongName"] }"
33
36
  puts " StandardName: #{ table_fragment["StandardName"] }"
@@ -68,6 +71,7 @@ module Rets
68
71
 
69
72
  def print_tree
70
73
  puts " LookupTable: #{name}"
74
+ puts " Resource: #{resource.id}"
71
75
  puts " Required: #{table_fragment['Required']}"
72
76
  puts " Searchable: #{ table_fragment["Searchable"] }"
73
77
  puts " Units: #{ table_fragment["Units"] }"
data/test/test_client.rb CHANGED
@@ -81,7 +81,6 @@ class TestClient < MiniTest::Test
81
81
  logger.debug "foo"
82
82
  end
83
83
 
84
-
85
84
  def test_find_first_calls_find_every_with_limit_one
86
85
  @client.expects(:find_every).with({:limit => 1, :foo => :bar}, nil).returns([1,2,3])
87
86
 
@@ -100,6 +99,25 @@ class TestClient < MiniTest::Test
100
99
  end
101
100
  end
102
101
 
102
+
103
+ def test_find_retries_when_receiving_no_records_found
104
+ @client.stubs(:find_every).raises(Rets::NoRecordsFound.new('')).then.returns([1])
105
+
106
+ assert_equal [1], @client.find(:all)
107
+ end
108
+
109
+ def test_find_does_not_retry_when_receiving_no_records_found_with_option
110
+ @client.stubs(:find_every).raises(Rets::NoRecordsFound.new(''))
111
+
112
+ assert_equal [], @client.find(:all, no_records_not_an_error: true)
113
+ end
114
+
115
+ def test_find_does_not_retry_and_returns_zero_on_count_request_when_receiving_no_records_found_with_option
116
+ @client.stubs(:find_every).raises(Rets::NoRecordsFound.new(''))
117
+
118
+ assert_equal 0, @client.find(:all, count: 2, no_records_not_an_error: true)
119
+ end
120
+
103
121
  def test_find_retries_on_errors
104
122
  @client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized')).then.raises(Rets::InvalidRequest.new(20134, 'Not Found')).then.returns([])
105
123
  @client.find(:all, :foo => :bar)
@@ -418,25 +418,25 @@ class TestMetadata < MiniTest::Test
418
418
  def test_table_initialize
419
419
  fragment = { "DataType" => "A", "SystemName" => "B" }
420
420
 
421
- table = Rets::Metadata::Table.new(fragment)
421
+ table = Rets::Metadata::Table.new(fragment, nil)
422
422
  assert_equal("A", table.type)
423
423
  assert_equal("B", table.name)
424
424
  end
425
425
 
426
426
  def test_table_resolve_returns_empty_string_when_value_nil
427
- table = Rets::Metadata::Table.new({})
427
+ table = Rets::Metadata::Table.new({}, nil)
428
428
 
429
429
  assert_equal "", table.resolve(nil)
430
430
  end
431
431
 
432
432
  def test_table_resolve_passes_values_straight_through
433
- table = Rets::Metadata::Table.new({})
433
+ table = Rets::Metadata::Table.new({}, nil)
434
434
 
435
435
  assert_equal "Foo", table.resolve("Foo")
436
436
  end
437
437
 
438
438
  def test_table_resolve_passes_values_strips_extra_whitspace
439
- table = Rets::Metadata::Table.new({})
439
+ table = Rets::Metadata::Table.new({}, nil)
440
440
 
441
441
  assert_equal "Foo", table.resolve(" Foo ")
442
442
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rets
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Estately, Inc. Open Source
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-26 00:00:00.000000000 Z
11
+ date: 2015-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.3.0
19
+ version: '2.4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.3.0
26
+ version: '2.4'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.5.2
33
+ version: '1.5'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.5.2
40
+ version: '1.5'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rdoc
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,42 +58,42 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.11.0
61
+ version: '0.11'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.11.0
68
+ version: '0.11'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: vcr
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 2.2.2
75
+ version: '2.2'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 2.2.2
82
+ version: '2.2'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: webmock
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 1.8.0
89
+ version: '1.8'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 1.8.0
96
+ version: '1.8'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: hoe
99
99
  requirement: !ruby/object:Gem::Requirement