rets 0.6.0 → 0.7.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.
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