rets 0.9.0 → 0.10.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 +4 -4
- data/CHANGELOG.md +19 -0
- data/Manifest.txt +24 -0
- data/README.md +73 -1
- data/Rakefile +1 -1
- data/lib/rets.rb +202 -1
- data/lib/rets/client.rb +83 -94
- data/lib/rets/http_client.rb +42 -0
- data/lib/rets/metadata.rb +15 -3
- data/lib/rets/metadata/caching.rb +59 -0
- data/lib/rets/metadata/file_cache.rb +29 -0
- data/lib/rets/metadata/json_serializer.rb +27 -0
- data/lib/rets/metadata/lookup_table.rb +65 -0
- data/lib/rets/metadata/lookup_type.rb +3 -4
- data/lib/rets/metadata/marshal_serializer.rb +27 -0
- data/lib/rets/metadata/multi_lookup_table.rb +70 -0
- data/lib/rets/metadata/null_cache.rb +24 -0
- data/lib/rets/metadata/resource.rb +39 -29
- data/lib/rets/metadata/rets_class.rb +27 -23
- data/lib/rets/metadata/rets_object.rb +32 -0
- data/lib/rets/metadata/table.rb +9 -101
- data/lib/rets/metadata/table_factory.rb +19 -0
- data/lib/rets/metadata/yaml_serializer.rb +27 -0
- data/lib/rets/parser/compact.rb +61 -18
- data/lib/rets/parser/error_checker.rb +8 -1
- data/test/fixtures.rb +58 -0
- data/test/test_caching.rb +89 -0
- data/test/test_client.rb +44 -24
- data/test/test_error_checker.rb +18 -0
- data/test/test_file_cache.rb +42 -0
- data/test/test_http_client.rb +96 -60
- data/test/test_json_serializer.rb +26 -0
- data/test/test_marshal_serializer.rb +26 -0
- data/test/test_metadata.rb +62 -450
- data/test/test_metadata_class.rb +50 -0
- data/test/test_metadata_lookup_table.rb +21 -0
- data/test/test_metadata_lookup_type.rb +12 -0
- data/test/test_metadata_multi_lookup_table.rb +60 -0
- data/test/test_metadata_object.rb +20 -0
- data/test/test_metadata_resource.rb +140 -0
- data/test/test_metadata_root.rb +151 -0
- data/test/test_metadata_table.rb +21 -0
- data/test/test_metadata_table_factory.rb +24 -0
- data/test/test_parser_compact.rb +23 -28
- data/test/test_yaml_serializer.rb +26 -0
- metadata +29 -5
data/lib/rets/http_client.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'http-cookie'
|
2
|
+
require 'httpclient'
|
3
|
+
|
1
4
|
module Rets
|
2
5
|
class HttpClient
|
3
6
|
attr_reader :http, :options, :logger, :login_url
|
@@ -10,6 +13,45 @@ module Rets
|
|
10
13
|
@options.fetch(:ca_certs, []).each {|c| @http.ssl_config.add_trust_ca(c) }
|
11
14
|
end
|
12
15
|
|
16
|
+
def self.from_options(options, logger)
|
17
|
+
if options[:http_proxy]
|
18
|
+
http = HTTPClient.new(options.fetch(:http_proxy))
|
19
|
+
|
20
|
+
if options[:proxy_username]
|
21
|
+
http.set_proxy_auth(options.fetch(:proxy_username), options.fetch(:proxy_password))
|
22
|
+
end
|
23
|
+
else
|
24
|
+
http = HTTPClient.new
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:receive_timeout]
|
28
|
+
http.receive_timeout = options[:receive_timeout]
|
29
|
+
end
|
30
|
+
|
31
|
+
if options[:cookie_store]
|
32
|
+
ensure_cookie_store_exists! options[:cookie_store]
|
33
|
+
http.set_cookie_store(options[:cookie_store])
|
34
|
+
end
|
35
|
+
|
36
|
+
http_client = new(http, options, logger, options[:login_url])
|
37
|
+
|
38
|
+
if options[:http_timing_stats_collector]
|
39
|
+
http_client = Rets::MeasuringHttpClient.new(http_client, options.fetch(:http_timing_stats_collector), options.fetch(:http_timing_stats_prefix))
|
40
|
+
end
|
41
|
+
|
42
|
+
if options[:lock_around_http_requests]
|
43
|
+
http_client = Rets::LockingHttpClient.new(http_client, options.fetch(:locker), options.fetch(:lock_name), options.fetch(:lock_options))
|
44
|
+
end
|
45
|
+
|
46
|
+
http_client
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.ensure_cookie_store_exists!(cookie_store)
|
50
|
+
unless File.exist? cookie_store
|
51
|
+
FileUtils.touch(cookie_store)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
13
55
|
def http_get(url, params=nil, extra_headers={})
|
14
56
|
http.set_auth(url, options[:username], options[:password])
|
15
57
|
headers = extra_headers.merge(rets_extra_headers)
|
data/lib/rets/metadata.rb
CHANGED
@@ -1,6 +1,18 @@
|
|
1
|
+
require 'rets/metadata/caching'
|
2
|
+
require 'rets/metadata/null_cache'
|
3
|
+
require 'rets/metadata/file_cache'
|
4
|
+
require 'rets/metadata/json_serializer'
|
5
|
+
require 'rets/metadata/yaml_serializer'
|
6
|
+
require 'rets/metadata/marshal_serializer'
|
7
|
+
|
1
8
|
require 'rets/metadata/containers'
|
2
|
-
|
3
|
-
require 'rets/metadata/resource'
|
4
|
-
require 'rets/metadata/rets_class'
|
9
|
+
|
5
10
|
require 'rets/metadata/root'
|
11
|
+
require 'rets/metadata/resource'
|
12
|
+
require 'rets/metadata/lookup_type'
|
13
|
+
require 'rets/metadata/table_factory'
|
6
14
|
require 'rets/metadata/table'
|
15
|
+
require 'rets/metadata/lookup_table'
|
16
|
+
require 'rets/metadata/multi_lookup_table'
|
17
|
+
require 'rets/metadata/rets_class'
|
18
|
+
require 'rets/metadata/rets_object'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Rets
|
2
|
+
module Metadata
|
3
|
+
|
4
|
+
# Metadata caching.
|
5
|
+
# @api internal
|
6
|
+
class Caching
|
7
|
+
|
8
|
+
# Given the options passed to Client#initialize, make an
|
9
|
+
# instance. Options:
|
10
|
+
#
|
11
|
+
# * :metadata_cache - Persistence mechanism. Defaults to
|
12
|
+
# NullCache.
|
13
|
+
#
|
14
|
+
# * "metadata_serializer - Serialization mechanism. Defaults to
|
15
|
+
# MarshalSerializer.
|
16
|
+
def self.make(options)
|
17
|
+
cache = options.fetch(:metadata_cache) { Metadata::NullCache.new }
|
18
|
+
serializer = options.fetch(:metadata_serializer) do
|
19
|
+
Metadata::MarshalSerializer.new
|
20
|
+
end
|
21
|
+
new(cache, serializer)
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :cache
|
25
|
+
attr_reader :serializer
|
26
|
+
|
27
|
+
# The cache is responsible for reading and writing the
|
28
|
+
# serialized metadata. The cache should quack like a
|
29
|
+
# Rets::Metadata::FileCache.
|
30
|
+
#
|
31
|
+
# The serializer is responsible for serializing/deserializing
|
32
|
+
# the metadata. The serializer should quack like a
|
33
|
+
# Rets::Metadata::MarshalSerializer.
|
34
|
+
def initialize(cache, serializer)
|
35
|
+
@cache = cache
|
36
|
+
@serializer = serializer
|
37
|
+
end
|
38
|
+
|
39
|
+
# Load metadata. Returns a Metadata::Root if successful, or nil
|
40
|
+
# if it could be loaded for any reason.
|
41
|
+
def load(logger)
|
42
|
+
sources = @cache.load do |file|
|
43
|
+
@serializer.load(file)
|
44
|
+
end
|
45
|
+
return nil unless sources.is_a?(Hash)
|
46
|
+
Metadata::Root.new(logger, sources)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Save metadata.
|
50
|
+
def save(metadata)
|
51
|
+
@cache.save do |file|
|
52
|
+
@serializer.save(file, metadata.sources)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rets
|
2
|
+
module Metadata
|
3
|
+
|
4
|
+
# This metadata cache persists the metadata to a file.
|
5
|
+
class FileCache
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
# Save the metadata. Should yield an IO-like object to a block;
|
12
|
+
# that block will serialize the metadata to that object.
|
13
|
+
def save(&block)
|
14
|
+
File.open(@path, "wb", &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Load the metadata. Should yield an IO-like object to a block;
|
18
|
+
# that block will deserialize the metadata from that object and
|
19
|
+
# return the metadata. Returns the metadata, or nil if it could
|
20
|
+
# not be loaded.
|
21
|
+
def load(&block)
|
22
|
+
File.open(@path, "rb", &block)
|
23
|
+
rescue IOError, SystemCallError
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Rets
|
4
|
+
module Metadata
|
5
|
+
|
6
|
+
# Serialize/Deserialize metadata using JSON.
|
7
|
+
class JsonSerializer
|
8
|
+
|
9
|
+
# Serialize to a file. The library reserves the right to change
|
10
|
+
# the type or contents of o, so don't depend on it being
|
11
|
+
# anything in particular.
|
12
|
+
def save(file, o)
|
13
|
+
file.write o.to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
# Deserialize from a file. If the metadata cannot be
|
17
|
+
# deserialized, return nil.
|
18
|
+
def load(file)
|
19
|
+
JSON.load(file)
|
20
|
+
rescue JSON::ParserError
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Rets
|
2
|
+
module Metadata
|
3
|
+
class LookupTable
|
4
|
+
attr_reader :resource_id, :lookup_types, :table_fragment, :name, :long_name
|
5
|
+
|
6
|
+
def initialize(resource_id, lookup_types, table_fragment)
|
7
|
+
@resource_id = resource_id
|
8
|
+
@lookup_types = lookup_types
|
9
|
+
|
10
|
+
@table_fragment = table_fragment
|
11
|
+
@name = table_fragment["SystemName"]
|
12
|
+
@long_name = table_fragment["LongName"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.build(table_fragment, resource_id, lookup_types)
|
16
|
+
lookup_name = table_fragment["LookupName"]
|
17
|
+
lookup_types = lookup_types[lookup_name]
|
18
|
+
new(resource_id, lookup_types, table_fragment)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Print the tree to a file
|
22
|
+
#
|
23
|
+
# [out] The file to print to. Defaults to $stdout.
|
24
|
+
def print_tree(out = $stdout)
|
25
|
+
out.puts " LookupTable: #{name}"
|
26
|
+
out.puts " Resource: #{resource_id}"
|
27
|
+
out.puts " Required: #{table_fragment['Required']}"
|
28
|
+
out.puts " Searchable: #{ table_fragment["Searchable"] }"
|
29
|
+
out.puts " Units: #{ table_fragment["Units"] }"
|
30
|
+
out.puts " ShortName: #{ table_fragment["ShortName"] }"
|
31
|
+
out.puts " LongName: #{ long_name }"
|
32
|
+
out.puts " StandardName: #{ table_fragment["StandardName"] }"
|
33
|
+
out.puts " Types:"
|
34
|
+
|
35
|
+
lookup_types.each do |lookup_type|
|
36
|
+
lookup_type.print_tree(out)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def lookup_type(value)
|
41
|
+
lookup_types.detect {|lt| lt.value == value }
|
42
|
+
end
|
43
|
+
|
44
|
+
def resolve(value)
|
45
|
+
if value.empty?
|
46
|
+
return value.to_s.strip
|
47
|
+
end
|
48
|
+
|
49
|
+
#Remove surrounding quotes
|
50
|
+
clean_value = value.scan(/^["']?(.*?)["']?$/).join
|
51
|
+
|
52
|
+
|
53
|
+
lookup_type = lookup_type(clean_value)
|
54
|
+
|
55
|
+
resolved_value = lookup_type ? lookup_type.long_value : nil
|
56
|
+
|
57
|
+
if resolved_value.nil? && $VERBOSE
|
58
|
+
warn("Discarding unmappable value of #{clean_value.inspect}")
|
59
|
+
end
|
60
|
+
|
61
|
+
resolved_value.to_s.strip
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
module Rets
|
2
2
|
module Metadata
|
3
3
|
class LookupType
|
4
|
-
|
4
|
+
attr_reader :long_value, :value
|
5
5
|
|
6
6
|
def initialize(lookup_type_fragment)
|
7
|
-
|
8
|
-
|
9
|
-
self.long_value = lookup_type_fragment["LongValue"]
|
7
|
+
@value = lookup_type_fragment["Value"]
|
8
|
+
@long_value = lookup_type_fragment["LongValue"]
|
10
9
|
end
|
11
10
|
|
12
11
|
# Print the tree to a file
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Rets
|
4
|
+
module Metadata
|
5
|
+
|
6
|
+
# Serialize/Deserialize metadata using Marshal.
|
7
|
+
class MarshalSerializer
|
8
|
+
|
9
|
+
# Serialize to a file. The library reserves the right to change
|
10
|
+
# the type or contents of o, so don't depend on it being
|
11
|
+
# anything in particular.
|
12
|
+
def save(file, o)
|
13
|
+
Marshal.dump(o, file)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Deserialize from a file. If the metadata cannot be
|
17
|
+
# deserialized, return nil.
|
18
|
+
def load(file)
|
19
|
+
Marshal.load(file)
|
20
|
+
rescue TypeError
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Rets
|
2
|
+
module Metadata
|
3
|
+
class MultiLookupTable
|
4
|
+
attr_reader :resource_id, :lookup_types, :table_fragment, :name, :long_name
|
5
|
+
|
6
|
+
def initialize(resource_id, lookup_types, table_fragment)
|
7
|
+
@resource_id = resource_id
|
8
|
+
@lookup_types = lookup_types
|
9
|
+
|
10
|
+
@table_fragment = table_fragment
|
11
|
+
@name = table_fragment["SystemName"]
|
12
|
+
@long_name = table_fragment["LongName"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.build(table_fragment, resource_id, lookup_types)
|
16
|
+
lookup_name = table_fragment["LookupName"]
|
17
|
+
lookup_types = lookup_types[lookup_name]
|
18
|
+
new(resource_id, lookup_types, table_fragment)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Print the tree to a file
|
22
|
+
#
|
23
|
+
# [out] The file to print to. Defaults to $stdout.
|
24
|
+
def print_tree(out = $stdout)
|
25
|
+
out.puts " MultiLookupTable: #{name}"
|
26
|
+
out.puts " Resource: #{resource_id}"
|
27
|
+
out.puts " Required: #{table_fragment['Required']}"
|
28
|
+
out.puts " Searchable: #{ table_fragment["Searchable"] }"
|
29
|
+
out.puts " Units: #{ table_fragment["Units"] }"
|
30
|
+
out.puts " ShortName: #{ table_fragment["ShortName"] }"
|
31
|
+
out.puts " LongName: #{ long_name }"
|
32
|
+
out.puts " StandardName: #{ table_fragment["StandardName"] }"
|
33
|
+
out.puts " Types:"
|
34
|
+
|
35
|
+
lookup_types.each do |lookup_type|
|
36
|
+
lookup_type.print_tree(out)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def lookup_type(value)
|
41
|
+
lookup_types.detect {|lt| lt.value == value }
|
42
|
+
end
|
43
|
+
|
44
|
+
def resolve(value)
|
45
|
+
if value.empty?
|
46
|
+
return []
|
47
|
+
end
|
48
|
+
|
49
|
+
values = value.split(",")
|
50
|
+
|
51
|
+
values = values.map do |v|
|
52
|
+
|
53
|
+
#Remove surrounding quotes
|
54
|
+
clean_value = v.scan(/^["']?(.*?)["']?$/).join
|
55
|
+
|
56
|
+
|
57
|
+
lookup_type = lookup_type(clean_value)
|
58
|
+
|
59
|
+
resolved_value = lookup_type ? lookup_type.long_value : nil
|
60
|
+
|
61
|
+
if resolved_value.nil? && $VERBOSE
|
62
|
+
warn("Discarding unmappable value of #{clean_value.inspect}")
|
63
|
+
end
|
64
|
+
|
65
|
+
resolved_value.to_s.strip
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rets
|
2
|
+
module Metadata
|
3
|
+
|
4
|
+
# This type of metadata cache, which is the default, neither saves
|
5
|
+
# nor restores.
|
6
|
+
class NullCache
|
7
|
+
|
8
|
+
# Save the metadata. Should yield an IO-like object to a block;
|
9
|
+
# that block will serialize the metadata to that object.
|
10
|
+
def save(&_block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Load the metadata. Should yield an IO-like object to a block;
|
14
|
+
# that block will deserialize the metadata from that object and
|
15
|
+
# return the metadata. Returns the metadata, or nil if it could
|
16
|
+
# not be loaded.
|
17
|
+
def load(&_block)
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -2,46 +2,44 @@ module Rets
|
|
2
2
|
module Metadata
|
3
3
|
class Resource
|
4
4
|
class MissingRetsClass < RuntimeError; end
|
5
|
-
|
6
|
-
attr_accessor :lookup_types
|
7
|
-
attr_accessor :key_field
|
5
|
+
attr_reader :id, :key_field, :rets_classes, :rets_objects
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
self.id = resource["ResourceID"]
|
16
|
-
self.key_field = resource["KeyField"]
|
7
|
+
def initialize(id, key_field, rets_classes, rets_objects)
|
8
|
+
@id = id
|
9
|
+
@key_field = key_field
|
10
|
+
@rets_classes = rets_classes
|
11
|
+
@rets_objects = rets_objects
|
17
12
|
end
|
18
13
|
|
19
|
-
def self.find_lookup_containers(metadata,
|
20
|
-
metadata[:lookup].select { |lc| lc.resource ==
|
14
|
+
def self.find_lookup_containers(metadata, resource_id)
|
15
|
+
metadata[:lookup].select { |lc| lc.resource == resource_id }
|
21
16
|
end
|
22
17
|
|
23
|
-
def self.find_lookup_type_containers(metadata,
|
24
|
-
metadata[:lookup_type].select { |ltc| ltc.resource ==
|
18
|
+
def self.find_lookup_type_containers(metadata, resource_id, lookup_name)
|
19
|
+
metadata[:lookup_type].select { |ltc| ltc.resource == resource_id && ltc.lookup == lookup_name }
|
25
20
|
end
|
26
21
|
|
27
|
-
def self.find_rets_classes(metadata,
|
28
|
-
class_container = metadata[:class].detect { |c| c.resource ==
|
22
|
+
def self.find_rets_classes(metadata, resource_id)
|
23
|
+
class_container = metadata[:class].detect { |c| c.resource == resource_id }
|
29
24
|
if class_container.nil?
|
30
|
-
raise MissingRetsClass.new("No Metadata classes for #{
|
25
|
+
raise MissingRetsClass.new("No Metadata classes for #{resource_id}")
|
31
26
|
else
|
32
27
|
class_container.classes
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
36
|
-
def self.
|
31
|
+
def self.find_rets_objects(metadata, resource_id)
|
32
|
+
metadata[:object].select { |object| object.resource == resource_id }.map(&:objects).flatten
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.build_lookup_tree(resource_id, metadata)
|
37
36
|
lookup_types = Hash.new {|h, k| h[k] = Array.new }
|
38
37
|
|
39
|
-
find_lookup_containers(metadata,
|
38
|
+
find_lookup_containers(metadata, resource_id).each do |lookup_container|
|
40
39
|
lookup_container.lookups.each do |lookup_fragment|
|
41
40
|
lookup_name = lookup_fragment["LookupName"]
|
42
41
|
|
43
|
-
find_lookup_type_containers(metadata,
|
44
|
-
|
42
|
+
find_lookup_type_containers(metadata, resource_id, lookup_name).each do |lookup_type_container|
|
45
43
|
lookup_type_container.lookup_types.each do |lookup_type_fragment|
|
46
44
|
lookup_types[lookup_name] << LookupType.new(lookup_type_fragment)
|
47
45
|
end
|
@@ -52,18 +50,27 @@ module Rets
|
|
52
50
|
lookup_types
|
53
51
|
end
|
54
52
|
|
55
|
-
def self.build_classes(
|
56
|
-
find_rets_classes(metadata,
|
57
|
-
RetsClass.build(rets_class_fragment,
|
53
|
+
def self.build_classes(resource_id, lookup_types, metadata)
|
54
|
+
find_rets_classes(metadata, resource_id).map do |rets_class_fragment|
|
55
|
+
RetsClass.build(rets_class_fragment, resource_id, lookup_types, metadata)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.build_objects(resource_id, metadata)
|
60
|
+
find_rets_objects(metadata, resource_id).map do |rets_object_fragment|
|
61
|
+
RetsObject.build(rets_object_fragment)
|
58
62
|
end
|
59
63
|
end
|
60
64
|
|
61
65
|
def self.build(resource_fragment, metadata, logger)
|
62
|
-
|
66
|
+
resource_id = resource_fragment["ResourceID"]
|
67
|
+
key_field = resource_fragment["KeyField"]
|
63
68
|
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
lookup_types = build_lookup_tree(resource_id, metadata)
|
70
|
+
rets_classes = build_classes(resource_id, lookup_types, metadata)
|
71
|
+
rets_objects = build_objects(resource_id, metadata)
|
72
|
+
|
73
|
+
new(resource_id, key_field, rets_classes, rets_objects)
|
67
74
|
rescue MissingRetsClass => e
|
68
75
|
logger.warn(e.message)
|
69
76
|
nil
|
@@ -77,6 +84,9 @@ module Rets
|
|
77
84
|
rets_classes.each do |rets_class|
|
78
85
|
rets_class.print_tree(out)
|
79
86
|
end
|
87
|
+
rets_objects.each do |rets_object|
|
88
|
+
rets_object.print_tree(out)
|
89
|
+
end
|
80
90
|
end
|
81
91
|
|
82
92
|
def find_rets_class(rets_class_name)
|