Skip to content

Commit

Permalink
Merge branch 'master' into pull-out-multi-lookup
Browse files Browse the repository at this point in the history
Conflicts:
	test/fixtures.rb
  • Loading branch information
dougcole committed Jan 27, 2016
2 parents bc2b52f + bc2314f commit ca86ee4
Show file tree
Hide file tree
Showing 18 changed files with 196 additions and 36 deletions.
50 changes: 50 additions & 0 deletions CODE_OF_CONDUCT.md
@@ -0,0 +1,50 @@
# Contributor Code of Conduct

As contributors and maintainers of this project, and in the interest of
fostering an open and welcoming community, we pledge to respect all people who
contribute through reporting issues, posting feature requests, updating
documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

By adopting this Code of Conduct, project maintainers commit themselves to
fairly and consistently applying these principles to every aspect of managing
this project. Project maintainers who do not follow or enforce the Code of
Conduct may be permanently removed from the project team.

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting a project maintainer at opensource@estately.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. Maintainers are
obligated to maintain confidentiality with regard to the reporter of an
incident.


This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 1.3.0, available at
[http://contributor-covenant.org/version/1/3/0/][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/3/0/
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -75,7 +75,7 @@ JSON or YAML instead, or define your own serialization mechanism, using the

client = Rets::Client.new(
...
metadata_serializer: Rets::Metadata::JsonSerializer
metadata_serializer: Rets::Metadata::JsonSerializer.new
)

The built-in serializers are:
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -9,7 +9,7 @@ Hoe.plugin :gemspec
Hoe.spec 'rets' do
developer 'Estately, Inc. Open Source', 'opensource@estately.com'

extra_deps << [ "httpclient", "~> 2.6.0" ]
extra_deps << [ "httpclient", "~> 2.7.0" ]
extra_deps << [ "http-cookie", "~> 1.0.0" ]
extra_deps << [ "nokogiri", "~> 1.5" ]

Expand Down
1 change: 1 addition & 0 deletions lib/rets.rb
Expand Up @@ -5,6 +5,7 @@
module Rets
VERSION = '0.9.0'

HttpError = Class.new(StandardError)
MalformedResponse = Class.new(ArgumentError)
UnknownResponse = Class.new(ArgumentError)
NoLogout = Class.new(ArgumentError)
Expand Down
22 changes: 12 additions & 10 deletions lib/rets/client.rb
@@ -1,8 +1,6 @@
require 'logger'

module Rets
class HttpError < StandardError ; end

class Client
COUNT = Struct.new(:exclude, :include, :only).new(0,1,2)
CASE_INSENSITIVE_PROC = Proc.new { |h,k| h.key?(k.downcase) ? h[k.downcase] : nil }
Expand Down Expand Up @@ -95,7 +93,10 @@ def find_with_given_retry(retries, opts)
else
handle_find_failure(retries, opts, e)
end
rescue AuthorizationFailure, InvalidRequest => e
rescue InvalidRequest, HttpError => e
handle_find_failure(retries, opts, e)
rescue AuthorizationFailure => e
login
handle_find_failure(retries, opts, e)
end
end
Expand Down Expand Up @@ -179,7 +180,7 @@ def all_objects(opts = {})
def objects(object_ids, opts = {})
response = case object_ids
when String then fetch_object(object_ids, opts)
when Array then fetch_object(object_ids.join(","), opts)
when Array then fetch_object(object_ids.join(":"), opts)
else raise ArgumentError, "Expected instance of String or Array, but got #{object_ids.inspect}."
end

Expand All @@ -202,10 +203,11 @@ def create_parts_from_response(response)

return parts
else
logger.debug "Rets::Client: Found 1 part (the whole body)"

# fake a multipart for interface compatibility
headers = {}
response.headers.each { |k,v| headers[k] = v[0] }

response.headers.each { |k,v| headers[k.downcase] = v }
part = Parser::Multipart::Part.new(headers, response.body)

return [part]
Expand Down Expand Up @@ -238,7 +240,7 @@ def fetch_object(object_id, opts = {})
http_post(capability_url("GetObject"), params, extra_headers)
end

def metadata
def metadata(types=nil)
return @metadata if @metadata
@cached_metadata ||= @caching.load(@logger)
if cached_metadata && (options[:skip_metadata_uptodate_check] ||
Expand All @@ -247,15 +249,15 @@ def metadata
@metadata = cached_metadata
else
client_progress.bad_cached_metadata(cached_metadata)
@metadata = Metadata::Root.new(logger, retrieve_metadata)
@metadata = Metadata::Root.new(logger, retrieve_metadata(types))
@caching.save(metadata)
end
@metadata
end

def retrieve_metadata
def retrieve_metadata(types=nil)
raw_metadata = {}
Metadata::METADATA_TYPES.each {|type|
(types || Metadata::METADATA_TYPES).each {|type|
raw_metadata[type] = retrieve_metadata_type(type)
}
raw_metadata
Expand Down
1 change: 1 addition & 0 deletions lib/rets/metadata.rb
Expand Up @@ -15,3 +15,4 @@
require 'rets/metadata/lookup_table'
require 'rets/metadata/multi_lookup_table'
require 'rets/metadata/rets_class'
require 'rets/metadata/rets_object'
21 changes: 18 additions & 3 deletions lib/rets/metadata/resource.rb
Expand Up @@ -2,12 +2,13 @@ module Rets
module Metadata
class Resource
class MissingRetsClass < RuntimeError; end
attr_reader :rets_classes, :id, :key_field
attr_reader :id, :key_field, :rets_classes, :rets_objects

def initialize(id, key_field, rets_classes)
def initialize(id, key_field, rets_classes, rets_objects)
@id = id
@key_field = key_field
@rets_classes = rets_classes
@rets_objects = rets_objects
end

def self.find_lookup_containers(metadata, resource_id)
Expand All @@ -27,6 +28,10 @@ def self.find_rets_classes(metadata, resource_id)
end
end

def self.find_rets_objects(metadata, resource_id)
metadata[:object].select { |object| object.resource == resource_id }.map(&:objects).flatten
end

def self.build_lookup_tree(resource_id, metadata)
lookup_types = Hash.new {|h, k| h[k] = Array.new }

Expand All @@ -51,14 +56,21 @@ def self.build_classes(resource_id, lookup_types, metadata)
end
end

def self.build_objects(resource_id, metadata)
find_rets_objects(metadata, resource_id).map do |rets_object_fragment|
RetsObject.build(rets_object_fragment)
end
end

def self.build(resource_fragment, metadata, logger)
resource_id = resource_fragment["ResourceID"]
key_field = resource_fragment["KeyField"]

lookup_types = build_lookup_tree(resource_id, metadata)
rets_classes = build_classes(resource_id, lookup_types, metadata)
rets_objects = build_objects(resource_id, metadata)

new(resource_id, key_field, rets_classes)
new(resource_id, key_field, rets_classes, rets_objects)
rescue MissingRetsClass => e
logger.warn(e.message)
nil
Expand All @@ -72,6 +84,9 @@ def print_tree(out = $stdout)
rets_classes.each do |rets_class|
rets_class.print_tree(out)
end
rets_objects.each do |rets_object|
rets_object.print_tree(out)
end
end

def find_rets_class(rets_class_name)
Expand Down
10 changes: 6 additions & 4 deletions lib/rets/metadata/rets_class.rb
@@ -1,12 +1,13 @@
module Rets
module Metadata
class RetsClass
attr_reader :name, :visible_name, :description, :tables
attr_reader :name, :visible_name, :standard_name, :description, :tables

def initialize(name, visible_name, description, tables)
def initialize(name, visible_name, standard_name, description, tables)
@name = name
@visible_name = visible_name
@description = description
@standard_name = standard_name

@tables = tables
end
Expand All @@ -28,11 +29,12 @@ def self.builds_tables(table_container, resource_id, lookup_types)
def self.build(rets_class_fragment, resource_id, lookup_types, metadata)
class_name = rets_class_fragment["ClassName"]
visible_name = rets_class_fragment["VisibleName"]
standard_name = rets_class_fragment["StandardName"]
description = rets_class_fragment["Description"]

table_container = find_table_container(metadata, resource_id, class_name)
tables = builds_tables(table_container, resource_id, lookup_types)
new(class_name, visible_name, description, tables)
new(class_name, visible_name, standard_name, description, tables)
end

# Print the tree to a file
Expand All @@ -48,7 +50,7 @@ def print_tree(out = $stdout)
end

def find_table(name)
tables.detect { |value| value.name == name }
tables.detect { |value| value.name.downcase == name.downcase }
end
end
end
Expand Down
32 changes: 32 additions & 0 deletions lib/rets/metadata/rets_object.rb
@@ -0,0 +1,32 @@
module Rets
module Metadata
class RetsObject
attr_reader :name, :mime_type, :description

def initialize(name, mime_type, description)
@name = name
@mime_type = mime_type
@description = description
end

def self.build(rets_object_fragment)
name = rets_object_fragment["VisibleName"]
mime_type = rets_object_fragment["MIMEType"]
description = rets_object_fragment["Description"]
new(name, mime_type, description)
end

def print_tree(out = $stdout)
out.puts " Object: #{name}"
out.puts " MimeType: #{mime_type}"
out.puts " Description: #{description}"
end

def ==(other)
name == other.name &&
mime_type == other.mime_type &&
description == other.description
end
end
end
end
9 changes: 4 additions & 5 deletions lib/rets/parser/compact.rb
@@ -1,5 +1,5 @@
# coding: utf-8

require 'cgi'
module Rets
module Parser
class Compact
Expand Down Expand Up @@ -87,10 +87,9 @@ def self.parse(columns, data, delimiter = nil)
def self.get_count(xml)
doc = Nokogiri.parse(xml.to_s)
if node = doc.at("//COUNT")
return node.attr('Records').to_i
elsif node = doc.at("//RETS-STATUS")
# Handle <RETS-STATUS ReplyCode="20201" ReplyText="No matching records were found" />
return 0 if node.attr('ReplyCode') == '20201'
node.attr('Records').to_i
else
0
end
end

Expand Down
6 changes: 3 additions & 3 deletions rets.gemspec
Expand Up @@ -24,7 +24,7 @@ Gem::Specification.new do |s|
s.specification_version = 4

if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<httpclient>, ["~> 2.6.0"])
s.add_runtime_dependency(%q<httpclient>, ["~> 2.7.0"])
s.add_runtime_dependency(%q<http-cookie>, ["~> 1.0.0"])
s.add_runtime_dependency(%q<nokogiri>, ["~> 1.5"])
s.add_development_dependency(%q<rdoc>, ["~> 4.0"])
Expand All @@ -33,7 +33,7 @@ Gem::Specification.new do |s|
s.add_development_dependency(%q<webmock>, ["~> 1.8"])
s.add_development_dependency(%q<hoe>, ["~> 3.13"])
else
s.add_dependency(%q<httpclient>, ["~> 2.6.0"])
s.add_dependency(%q<httpclient>, ["~> 2.7.0"])
s.add_dependency(%q<http-cookie>, ["~> 1.0.0"])
s.add_dependency(%q<nokogiri>, ["~> 1.5"])
s.add_dependency(%q<rdoc>, ["~> 4.0"])
Expand All @@ -43,7 +43,7 @@ Gem::Specification.new do |s|
s.add_dependency(%q<hoe>, ["~> 3.13"])
end
else
s.add_dependency(%q<httpclient>, ["~> 2.6.0"])
s.add_dependency(%q<httpclient>, ["~> 2.7.0"])
s.add_dependency(%q<http-cookie>, ["~> 1.0.0"])
s.add_dependency(%q<nokogiri>, ["~> 1.5"])
s.add_dependency(%q<rdoc>, ["~> 4.0"])
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures.rb
Expand Up @@ -309,4 +309,7 @@
Types:
Quarterly -> Q
Annually -> A
Object: Photo
MimeType: photo/jpg
Description: photo description
EOF
15 changes: 12 additions & 3 deletions test/test_client.rb
Expand Up @@ -183,17 +183,26 @@ def test_find_does_not_retry_and_returns_zero_on_count_request_when_receiving_no

def test_find_retries_on_errors
@client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized')).then.raises(Rets::InvalidRequest.new(20134, 'Not Found')).then.returns([])
@client.stubs(:login)
@client.find(:all, :foo => :bar)
end

def test_find_eventually_reraises_errors
@client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized'))
@client.stubs(:login)

assert_raises Rets::AuthorizationFailure do
@client.find(:all, :foo => :bar)
end
end

def test_find_logs_in_after_auth_error
@client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized')).then.returns(["foo"])

@client.expects(:login)
@client.find(:all, :foo => :bar)
end

def test_all_objects_calls_objects
@client.expects(:objects).with("*", :foo => :bar)

Expand All @@ -208,10 +217,10 @@ def test_objects_handles_string_argument
end

def test_objects_handle_array_argument
@client.expects(:fetch_object).with("1,2", :foo => :bar)
@client.expects(:fetch_object).with("1:2:3", :foo => :bar)
@client.stubs(:create_parts_from_response)

@client.objects([1,2], :foo => :bar)
@client.objects([1,2,3], :foo => :bar)
end

def test_objects_raises_on_other_arguments
Expand Down Expand Up @@ -247,7 +256,7 @@ def test_parse_boundary_wo_quotes
def test_create_parts_from_response_returns_a_single_part_when_not_multipart_response
response = {}
response.stubs(:header => { "content-type" => ['text/plain']})
response.stubs(:headers => { "content-type" => ['text/plain']})
response.stubs(:headers => { "Content-Type" => 'text/plain'})
response.stubs(:body => "fakebody")

parts = @client.create_parts_from_response(response)
Expand Down

0 comments on commit ca86ee4

Please sign in to comment.