flexmls_api 0.3.2
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.
- data/Gemfile +20 -0
- data/Gemfile.lock +60 -0
- data/LICENSE +14 -0
- data/README.md +128 -0
- data/Rakefile +78 -0
- data/VERSION +1 -0
- data/lib/flexmls_api/authentication.rb +104 -0
- data/lib/flexmls_api/client.rb +20 -0
- data/lib/flexmls_api/configuration.rb +40 -0
- data/lib/flexmls_api/faraday.rb +52 -0
- data/lib/flexmls_api/models/base.rb +76 -0
- data/lib/flexmls_api/models/connect_prefs.rb +10 -0
- data/lib/flexmls_api/models/contact.rb +25 -0
- data/lib/flexmls_api/models/custom_fields.rb +12 -0
- data/lib/flexmls_api/models/document.rb +11 -0
- data/lib/flexmls_api/models/idx_link.rb +45 -0
- data/lib/flexmls_api/models/listing.rb +110 -0
- data/lib/flexmls_api/models/market_statistics.rb +33 -0
- data/lib/flexmls_api/models/photo.rb +15 -0
- data/lib/flexmls_api/models/property_types.rb +7 -0
- data/lib/flexmls_api/models/standard_fields.rb +7 -0
- data/lib/flexmls_api/models/subresource.rb +13 -0
- data/lib/flexmls_api/models/system_info.rb +7 -0
- data/lib/flexmls_api/models/video.rb +16 -0
- data/lib/flexmls_api/models/virtual_tour.rb +18 -0
- data/lib/flexmls_api/models.rb +21 -0
- data/lib/flexmls_api/paginate.rb +87 -0
- data/lib/flexmls_api/request.rb +172 -0
- data/lib/flexmls_api/version.rb +4 -0
- data/lib/flexmls_api.rb +41 -0
- data/spec/fixtures/contacts.json +25 -0
- data/spec/fixtures/listing_document_index.json +19 -0
- data/spec/fixtures/listing_no_subresources.json +38 -0
- data/spec/fixtures/listing_photos_index.json +469 -0
- data/spec/fixtures/listing_videos_index.json +18 -0
- data/spec/fixtures/listing_virtual_tours_index.json +42 -0
- data/spec/fixtures/listing_with_documents.json +52 -0
- data/spec/fixtures/listing_with_photos.json +110 -0
- data/spec/fixtures/listing_with_supplement.json +39 -0
- data/spec/fixtures/listing_with_videos.json +54 -0
- data/spec/fixtures/listing_with_vtour.json +48 -0
- data/spec/fixtures/session.json +10 -0
- data/spec/json_helper.rb +77 -0
- data/spec/spec_helper.rb +78 -0
- data/spec/unit/flexmls_api/configuration_spec.rb +97 -0
- data/spec/unit/flexmls_api/faraday_spec.rb +94 -0
- data/spec/unit/flexmls_api/models/base_spec.rb +62 -0
- data/spec/unit/flexmls_api/models/connect_prefs_spec.rb +9 -0
- data/spec/unit/flexmls_api/models/contact_spec.rb +70 -0
- data/spec/unit/flexmls_api/models/document_spec.rb +39 -0
- data/spec/unit/flexmls_api/models/listing_spec.rb +174 -0
- data/spec/unit/flexmls_api/models/photo_spec.rb +59 -0
- data/spec/unit/flexmls_api/models/property_types_spec.rb +20 -0
- data/spec/unit/flexmls_api/models/standard_fields_spec.rb +42 -0
- data/spec/unit/flexmls_api/models/system_info_spec.rb +37 -0
- data/spec/unit/flexmls_api/models/video_spec.rb +43 -0
- data/spec/unit/flexmls_api/models/virtual_tour_spec.rb +46 -0
- data/spec/unit/flexmls_api/paginate_spec.rb +221 -0
- data/spec/unit/flexmls_api/request_spec.rb +288 -0
- data/spec/unit/flexmls_api_spec.rb +44 -0
- metadata +315 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module FlexmlsApi
|
2
|
+
module FaradayExt
|
3
|
+
#=Flexmls API Faraday middle way
|
4
|
+
# HTTP Response after filter to package api responses and bubble up basic api errors.
|
5
|
+
class FlexmlsMiddleware < Faraday::Response::Middleware
|
6
|
+
begin
|
7
|
+
def self.register_on_complete(env)
|
8
|
+
env[:response].on_complete do |finished_env|
|
9
|
+
FlexmlsApi.logger.debug("hashed response: #{finished_env[:body].inspect}")
|
10
|
+
validate_and_build_response(finished_env)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
rescue LoadError, NameError => e
|
14
|
+
self.load_error = e
|
15
|
+
end
|
16
|
+
|
17
|
+
# Handles pretty much all the api response parsing and error handling. All responses that
|
18
|
+
# indicate a failure will raise a FlexmlsApi::ClientError exception
|
19
|
+
def self.validate_and_build_response(finished_env)
|
20
|
+
body = finished_env[:body]
|
21
|
+
FlexmlsApi.logger.debug("Response Body: #{body.inspect}")
|
22
|
+
unless body.is_a?(Hash) && body.key?("D")
|
23
|
+
raise InvalidResponse, "The server response could not be understood"
|
24
|
+
end
|
25
|
+
response = ApiResponse.new body
|
26
|
+
case finished_env[:status]
|
27
|
+
when 400, 409
|
28
|
+
raise BadResourceRequest.new(response.code, finished_env[:status]), response.message
|
29
|
+
when 401
|
30
|
+
raise PermissionDenied.new(response.code, finished_env[:status]), response.message
|
31
|
+
when 404
|
32
|
+
raise NotFound.new(response.code, finished_env[:status]), response.message
|
33
|
+
when 405
|
34
|
+
raise NotAllowed.new(response.code, finished_env[:status]), response.message
|
35
|
+
when 500
|
36
|
+
raise ClientError.new(response.code, finished_env[:status]), response.message
|
37
|
+
when 200..299
|
38
|
+
FlexmlsApi.logger.debug("Success!")
|
39
|
+
else
|
40
|
+
raise ClientError.new(response.code, finished_env[:status]), response.message
|
41
|
+
end
|
42
|
+
finished_env[:body] = response
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(app)
|
46
|
+
super
|
47
|
+
@parser = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module FlexmlsApi
|
2
|
+
module Models
|
3
|
+
# =API Model Base class
|
4
|
+
# Intended to be a lot like working with ActiveResource, this class adds most of the basic
|
5
|
+
# active model type niceties.
|
6
|
+
class Base
|
7
|
+
extend Paginate
|
8
|
+
|
9
|
+
attr_accessor :attributes
|
10
|
+
|
11
|
+
# Name of the resource as related to the path name
|
12
|
+
def self.element_name
|
13
|
+
# TODO I'd love to pull in active model at this point to provide default naming
|
14
|
+
@element_name ||= "resource"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.element_name=(name)
|
18
|
+
@element_name = name
|
19
|
+
end
|
20
|
+
|
21
|
+
# Resource path prefix, prepended to the url
|
22
|
+
def self.prefix
|
23
|
+
@prefix ||= "/"
|
24
|
+
end
|
25
|
+
def self.prefix=(prefix)
|
26
|
+
@prefix = prefix
|
27
|
+
end
|
28
|
+
def self.path
|
29
|
+
"#{prefix}#{element_name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.connection
|
33
|
+
FlexmlsApi.client
|
34
|
+
end
|
35
|
+
def connection
|
36
|
+
self.class.connection
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(attributes={})
|
40
|
+
@attributes = {}
|
41
|
+
load(attributes)
|
42
|
+
end
|
43
|
+
|
44
|
+
def load(attributes)
|
45
|
+
attributes.each do |key,val|
|
46
|
+
@attributes[key.to_s] = val
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.get(options={})
|
51
|
+
collect(connection.get(path, options))
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.first(options={})
|
55
|
+
get(options)[0]
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(method_symbol, *arguments)
|
59
|
+
method_name = method_symbol.to_s
|
60
|
+
|
61
|
+
if method_name =~ /(=|\?)$/
|
62
|
+
case $1
|
63
|
+
when "="
|
64
|
+
attributes[$`] = arguments.first
|
65
|
+
# TODO figure out a nice way to present setters for the standard fields
|
66
|
+
when "?"
|
67
|
+
attributes[$`]
|
68
|
+
end
|
69
|
+
else
|
70
|
+
return attributes[method_name] if attributes.include?(method_name)
|
71
|
+
super # GTFO
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module FlexmlsApi
|
2
|
+
module Models
|
3
|
+
class Contact < Base
|
4
|
+
self.element_name="contacts"
|
5
|
+
|
6
|
+
def save
|
7
|
+
begin
|
8
|
+
return save!
|
9
|
+
rescue BadResourceRequest => e
|
10
|
+
rescue NotFound => e
|
11
|
+
# log and leave
|
12
|
+
FlexmlsApi.logger.error("Failed to save contact #{self}: #{e.message}")
|
13
|
+
end
|
14
|
+
false
|
15
|
+
end
|
16
|
+
def save!
|
17
|
+
results = connection.post self.class.path, "Contacts" => [ attributes ]
|
18
|
+
result = results.first
|
19
|
+
attributes['ResourceUri'] = result['ResourceUri']
|
20
|
+
attributes['Id'] = result['ResourceUri'][/\/.*\/(.+)$/, 1]
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module FlexmlsApi
|
2
|
+
module Models
|
3
|
+
class IdxLink < Base
|
4
|
+
self.element_name="idxlinks"
|
5
|
+
|
6
|
+
#TODO Work all below into common base class
|
7
|
+
def self.find(*arguments)
|
8
|
+
scope = arguments.slice!(0)
|
9
|
+
options = arguments.slice!(0) || {}
|
10
|
+
|
11
|
+
case scope
|
12
|
+
when :all then find_every(options)
|
13
|
+
when :first then find_every(options).first
|
14
|
+
when :last then find_every(options).last
|
15
|
+
when :one then find_one(options)
|
16
|
+
else find_single(scope, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.first(*arguments)
|
21
|
+
find(:first, *arguments)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.last(*arguments)
|
25
|
+
find(:last, *arguments)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def self.find_every(options)
|
31
|
+
raise NotImplementedError # TODO
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.find_one(options)
|
35
|
+
raise NotImplementedError # TODO
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find_single(scope, options)
|
39
|
+
resp = FlexmlsApi.client.get("/idxlinks/#{scope}", options)
|
40
|
+
new(resp[0])
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module FlexmlsApi
|
2
|
+
module Models
|
3
|
+
class Listing < Base
|
4
|
+
attr_accessor :photos, :videos, :virtual_tours, :documents
|
5
|
+
self.element_name="listings"
|
6
|
+
|
7
|
+
def initialize(attributes={})
|
8
|
+
@photos = []
|
9
|
+
@videos = []
|
10
|
+
@virtual_tours = []
|
11
|
+
@documents = []
|
12
|
+
|
13
|
+
|
14
|
+
if attributes.has_key?('StandardFields')
|
15
|
+
pics, vids, tours, docs = attributes['StandardFields'].values_at('Photos','Videos', 'VirtualTours', 'Documents')
|
16
|
+
end
|
17
|
+
|
18
|
+
if pics != nil
|
19
|
+
pics.collect { |pic| @photos.push(Photo.new(pic)) }
|
20
|
+
attributes['StandardFields'].delete('Photos')
|
21
|
+
end
|
22
|
+
|
23
|
+
if vids != nil
|
24
|
+
vids.collect { |vid| @videos.push(Video.new(vid)) }
|
25
|
+
attributes['StandardFields'].delete('Videos')
|
26
|
+
end
|
27
|
+
|
28
|
+
if tours != nil
|
29
|
+
tours.collect { |tour| @virtual_tours.push(VirtualTour.new(tour)) }
|
30
|
+
attributes['StandardFields'].delete('VirtualTours')
|
31
|
+
end
|
32
|
+
|
33
|
+
if docs != nil
|
34
|
+
docs.collect { |doc| @documents.push(Document.new(doc)) }
|
35
|
+
attributes['StandardFields'].delete('Documents')
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
super(attributes)
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def self.find(*arguments)
|
44
|
+
scope = arguments.slice!(0)
|
45
|
+
options = arguments.slice!(0) || {}
|
46
|
+
|
47
|
+
case scope
|
48
|
+
when :all then find_every(options)
|
49
|
+
when :first then find_every(options).first
|
50
|
+
when :last then find_every(options).last
|
51
|
+
when :one then find_one(options)
|
52
|
+
else find_single(scope, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.find_by_cart_id(cart_id, owner, options={})
|
57
|
+
options.merge!({ :ApiUser => owner, :_filter => "ListingCart Eq '#{cart_id}'" })
|
58
|
+
find(:all, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.first(*arguments)
|
62
|
+
find(:first, *arguments)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.last(*arguments)
|
66
|
+
find(:last, *arguments)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.my(arguments={})
|
70
|
+
collect(connection.get("/my/listings", arguments))
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def self.find_every(options)
|
77
|
+
collect(connection.get('/listings', options))
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.find_one(options)
|
81
|
+
raise NotImplementedError # TODO
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.find_single(scope, options)
|
85
|
+
resp = FlexmlsApi.client.get("/listings/#{scope}", options)
|
86
|
+
new(resp[0])
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# TODO trim this down so we're only overriding the StandardFields access
|
91
|
+
def method_missing(method_symbol, *arguments)
|
92
|
+
method_name = method_symbol.to_s
|
93
|
+
|
94
|
+
if method_name =~ /(=|\?)$/
|
95
|
+
case $1
|
96
|
+
when "="
|
97
|
+
attributes[$`] = arguments.first
|
98
|
+
# TODO figure out a nice way to present setters for the standard fields
|
99
|
+
when "?"
|
100
|
+
attributes[$`]
|
101
|
+
end
|
102
|
+
else
|
103
|
+
return attributes[method_name] if attributes.include?(method_name)
|
104
|
+
return @attributes['StandardFields'][method_name] if attributes['StandardFields'].include?(method_name)
|
105
|
+
super # GTFO
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module FlexmlsApi
|
2
|
+
module Models
|
3
|
+
class MarketStatistics < Base
|
4
|
+
self.element_name="marketstatistics"
|
5
|
+
|
6
|
+
def self.absorption(parameters={})
|
7
|
+
self.stat('absorption',parameters)
|
8
|
+
end
|
9
|
+
def self.inventory(parameters={})
|
10
|
+
self.stat('inventory',parameters)
|
11
|
+
end
|
12
|
+
def self.price(parameters={})
|
13
|
+
self.stat('price',parameters)
|
14
|
+
end
|
15
|
+
def self.ratio(parameters={})
|
16
|
+
self.stat('ratio',parameters)
|
17
|
+
end
|
18
|
+
def self.dom(parameters={})
|
19
|
+
self.stat('dom',parameters)
|
20
|
+
end
|
21
|
+
def self.volume(parameters={})
|
22
|
+
self.stat('volume',parameters)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def self.stat(stat_name, parameters={})
|
27
|
+
resp = connection.get("#{path}/#{stat_name}", parameters)
|
28
|
+
new(resp[0])
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|