flexmls_api 0.7.0 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ == v0.7.1 2011-10-13
2
+ * Updated connection dependencies
3
+ * Added messaging model
4
+ * ExpirationDate editable for Listing
1
5
  == v0.7.0 2011-10-03
2
6
  * Major changes to results object returned from the client (and models). Results are no longer allowed to be nil, and Array is decorated with non result response information.
3
7
  * Finished listing photo uploads
data/README.md CHANGED
@@ -24,8 +24,9 @@ Usage Examples
24
24
  # - ssl: false
25
25
  # - user_agent: 'flexmls API Ruby Gem'
26
26
  FlexmlsApi.configure do |config|
27
- config.api_key = 'your_api_key'
28
- config.api_secret = 'your_api_secret'
27
+ config.endpoint = 'http://api.developers.flexmls.com'
28
+ config.api_key = 'my_api_key'
29
+ config.api_secret = 'my_api_secret'
29
30
  end
30
31
 
31
32
  # mixin the models so you can use them without prefix
@@ -38,23 +39,22 @@ Usage Examples
38
39
  #### Interactive Console
39
40
  Included in the gem is a simple setup script to run the client in IRB. To use it, first create the file called _.flexmls_api_testing_ filling in the credentials for your account.
40
41
 
41
- API_USER="20110101000000000000000000" # ID for an api user
42
42
  API_ENDPOINT="http://api.developers.flexmls.com"
43
- API_KEY="my_test_key"
44
- API_SECRET="my_test_secret"
43
+ API_KEY="my_api_key"
44
+ API_SECRET="my_api_secret"
45
45
 
46
46
  export API_USER API_ENDPOINT API_KEY API_SECRET
47
47
 
48
48
  Now, to run with this setup, run the following from the command line:
49
49
 
50
50
  > source .flexmls_api_testing
51
- > bin/flexmls_api
51
+ > flexmls_api
52
52
  flemxlsApi> FlexmlsApi.client.get '/my/account'
53
53
 
54
- You can also provide these options from the command line, see "script/console -h" for more information
54
+ You can also provide these options from the command line, see "flexmls_api -h" for more information
55
55
 
56
56
 
57
- Authentication
57
+ Authentication Types
58
58
  --------------
59
59
  Authentication is handled transparently by the request framework in the gem, so you should never need to manually make an authentication request. More than one mode of authentication is supported, so the client needs to be configured accordingly.
60
60
 
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require "rubygems"
2
+ require 'rubygems/user_interaction'
2
3
  require 'flexmls_gems/tasks'
3
4
  require 'flexmls_gems/tasks/spec'
4
5
  require 'flexmls_gems/tasks/rdoc'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.7.3
@@ -1,5 +1,4 @@
1
1
  require 'rubygems'
2
- require 'curb'
3
2
  require 'json'
4
3
  require 'logger'
5
4
 
@@ -10,6 +9,7 @@ require 'flexmls_api/authentication'
10
9
  require 'flexmls_api/response'
11
10
  require 'flexmls_api/paginate'
12
11
  require 'flexmls_api/request'
12
+ require 'flexmls_api/connection'
13
13
  require 'flexmls_api/client'
14
14
  require 'flexmls_api/faraday'
15
15
  require 'flexmls_api/primary_array'
@@ -50,37 +50,6 @@ module FlexmlsApi
50
50
  def session=(active_session)
51
51
  @authenticator.session=active_session
52
52
  end
53
-
54
- # Main connection object for running requests. Bootstraps the Faraday abstraction layer with
55
- # our client configuration.
56
- def connection(force_ssl = false)
57
- opts = {
58
- :headers => headers
59
- }
60
- domain = @endpoint
61
- if(force_ssl || self.ssl)
62
- opts[:ssl] = {:verify => false }
63
- opts[:url] = @endpoint.sub /^http:/, "https:"
64
- else
65
- opts[:url] = @endpoint.sub /^https:/, "http:"
66
- end
67
- conn = Faraday::Connection.new(opts) do |builder|
68
- builder.adapter Faraday.default_adapter
69
- builder.use FlexmlsApi::FaradayExt::FlexmlsMiddleware
70
- end
71
- FlexmlsApi.logger.debug("Connection: #{conn.inspect}")
72
- conn
73
- end
74
-
75
- # HTTP request headers for client requests
76
- def headers
77
- {
78
- :accept => 'application/json',
79
- :content_type => 'application/json',
80
- :user_agent => Configuration::DEFAULT_USER_AGENT,
81
- Configuration::X_FLEXMLS_API_USER_AGENT => user_agent
82
- }
83
- end
84
-
53
+
85
54
  end
86
55
  end
@@ -170,6 +170,15 @@ module FlexmlsApi
170
170
  require 'flexmls_api/authentication/oauth2_impl/grant_type_code'
171
171
  require 'flexmls_api/authentication/oauth2_impl/grant_type_password'
172
172
  require 'flexmls_api/authentication/oauth2_impl/password_provider'
173
+
174
+ # Loads a provider class from a string
175
+ def self.load_provider(string, args={})
176
+ constant = Object
177
+ string.split("::").compact.each { |name| constant = constant.const_get(name) unless name == ""}
178
+ constant.new(args)
179
+ rescue => e
180
+ raise ArgumentError, "The value '#{string}' is an invalid class name for an oauth2 provider: #{e.message}"
181
+ end
173
182
  end
174
183
 
175
184
  end
@@ -4,6 +4,7 @@ module FlexmlsApi
4
4
  # FlexmlsApi::client if the global configuration has been set as well. Otherwise, this class may
5
5
  # be instanciated separately with the configuration information.
6
6
  class Client
7
+ include Connection
7
8
  include Authentication
8
9
  include Request
9
10
 
@@ -1,35 +1,22 @@
1
1
  require 'erb'
2
+
2
3
  module FlexmlsApi
3
4
  module Configuration
4
5
  class YamlConfig
5
6
  KEY_CONFIGURATIONS = VALID_OPTION_KEYS + [:oauth2] + OAUTH2_KEYS
6
-
7
+ DEFAULT_OAUTH2_PROVIDER = "FlexmlsApi::Authentication::OAuth2Impl::PasswordProvider"
7
8
  attr_accessor *KEY_CONFIGURATIONS
8
- attr_reader :client_keys, :oauth2_keys
9
+ attr_reader :client_keys, :oauth2_keys, :provider
9
10
 
10
11
  def initialize(filename=nil)
11
- @client_keys = {}
12
- @oauth2_keys = {}
13
12
  @oauth2 = false
14
13
  load_file(filename) unless filename.nil?()
15
14
  end
16
15
  def load_file(file)
17
- @client_keys = {}
18
- @oauth2_keys = {}
19
16
  @file = file
20
17
  @name = File.basename(file, ".yml")
21
18
  config = YAML.load(ERB.new(File.read(file)).result)[api_env]
22
- config.each do |key,val|
23
- sym = key.to_sym
24
- if KEY_CONFIGURATIONS.include? sym
25
- self.send("#{sym}=", val)
26
- if VALID_OPTION_KEYS.include?(sym)
27
- @client_keys[sym] = val
28
- elsif OAUTH2_KEYS.include?(sym)
29
- @oauth2_keys[sym] = val
30
- end
31
- end
32
- end
19
+ config["oauth2"] == true ? load_oauth2(config) : load_api_auth(config)
33
20
  rescue => e
34
21
  FlexmlsApi.logger().error("Unable to load config file #{file}[#{api_env}]")
35
22
  raise e
@@ -54,7 +41,7 @@ module FlexmlsApi
54
41
 
55
42
  # Used to specify the root of where to look for flexmlsApi config files
56
43
  def self.config_path
57
- "config/flexmls_api"
44
+ path_prefix + "config/flexmls_api"
58
45
  end
59
46
 
60
47
  def self.config_keys()
@@ -74,8 +61,41 @@ module FlexmlsApi
74
61
  def env
75
62
  ENV
76
63
  end
77
-
64
+
65
+ private
66
+ def load_api_auth(config={})
67
+ @client_keys = {}
68
+ @oauth2_keys = {}
69
+ config.each do |key,val|
70
+ sym = key.to_sym
71
+ if VALID_OPTION_KEYS.include?(sym)
72
+ self.send("#{sym}=", val)
73
+ @client_keys[sym] = val
74
+ end
75
+ end
76
+ end
77
+ def load_oauth2(config={})
78
+ @oauth2_provider = DEFAULT_OAUTH2_PROVIDER
79
+ @client_keys = {:oauth2_provider => @oauth2_provider }
80
+ @oauth2_keys = {}
81
+ @oauth2 = true
82
+ config.each do |key,val|
83
+ sym = key.to_sym
84
+ if VALID_OPTION_KEYS.include?(sym)
85
+ self.send("#{sym}=", val)
86
+ @client_keys[sym] = val
87
+ elsif OAUTH2_KEYS.include? sym
88
+ self.send("#{sym}=", val)
89
+ @oauth2_keys[sym] = val
90
+ end
91
+ end
92
+ end
93
+ # In a rails app, default to the rails root, regardless of where that may be
94
+ def self.path_prefix
95
+ "#{Rails.root}/"
96
+ rescue => e
97
+ ""
98
+ end
78
99
  end
79
100
  end
80
101
  end
81
-
@@ -0,0 +1,42 @@
1
+ require 'openssl'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+ require 'yajl'
5
+
6
+ module FlexmlsApi
7
+ # =Connection
8
+ # Mixin module for handling http connection information
9
+ module Connection
10
+ # Main connection object for running requests. Bootstraps the Faraday abstraction layer with
11
+ # our client configuration.
12
+ def connection(force_ssl = false)
13
+ opts = {
14
+ :headers => headers
15
+ }
16
+ domain = @endpoint
17
+ if(force_ssl || self.ssl)
18
+ opts[:ssl] = {:verify => false }
19
+ opts[:url] = @endpoint.sub /^http:/, "https:"
20
+ else
21
+ opts[:url] = @endpoint.sub /^https:/, "http:"
22
+ end
23
+ conn = Faraday::Connection.new(opts) do |builder|
24
+ builder.adapter Faraday.default_adapter
25
+ builder.use FlexmlsApi::FaradayExt::FlexmlsMiddleware
26
+ end
27
+ FlexmlsApi.logger.debug("Connection: #{conn.inspect}")
28
+ conn
29
+ end
30
+
31
+ # HTTP request headers for client requests
32
+ def headers
33
+ {
34
+ :accept => 'application/json',
35
+ :content_type => 'application/json',
36
+ :user_agent => Configuration::DEFAULT_USER_AGENT,
37
+ Configuration::X_FLEXMLS_API_USER_AGENT => user_agent
38
+ }
39
+ end
40
+
41
+ end
42
+ end
@@ -24,8 +24,15 @@ module FlexmlsApi
24
24
  end
25
25
  end
26
26
  case finished_env[:status]
27
- when 400, 409
28
- raise BadResourceRequest, {:message => response.message, :code => response.code, :status => finished_env[:status]}
27
+ when 400
28
+ hash = {:message => response.message, :code => response.code, :status => finished_env[:status]}
29
+
30
+ # constraint violation
31
+ if response.code == 1053
32
+ details = body['D']['Details']
33
+ hash[:details] = details
34
+ end
35
+ raise BadResourceRequest,hash
29
36
  when 401
30
37
  # Handle the WWW-Authenticate Response Header Field if present. This can be returned by
31
38
  # OAuth2 implementations and wouldn't hurt to log.
@@ -35,7 +42,9 @@ module FlexmlsApi
35
42
  when 404
36
43
  raise NotFound, {:message => response.message, :code => response.code, :status => finished_env[:status]}
37
44
  when 405
38
- raise NotAllowed, {:message => response.message, :code => response.code, :status => finished_env[:status]}
45
+ raise NotAllowed, {:message => response.message, :code => response.code, :status => finished_env[:status]}
46
+ when 409
47
+ raise BadResourceRequest, {:message => response.message, :code => response.code, :status => finished_env[:status]}
39
48
  when 500
40
49
  raise ClientError, {:message => response.message, :code => response.code, :status => finished_env[:status]}
41
50
  when 200..299
@@ -51,6 +60,5 @@ module FlexmlsApi
51
60
  end
52
61
 
53
62
  end
54
-
55
63
  end
56
64
  end
@@ -23,6 +23,7 @@ require 'flexmls_api/models/note'
23
23
  require 'flexmls_api/models/listing_cart'
24
24
  require 'flexmls_api/models/shared_listing'
25
25
  require 'flexmls_api/models/saved_search'
26
+ require 'flexmls_api/models/message'
26
27
 
27
28
  module FlexmlsApi
28
29
  module Models
@@ -6,7 +6,8 @@ module FlexmlsApi
6
6
  class Base
7
7
  extend Paginate
8
8
 
9
- attr_accessor :attributes
9
+ attr_accessor :attributes, :errors
10
+ attr_reader :changed
10
11
 
11
12
  # Name of the resource as related to the path name
12
13
  def self.element_name
@@ -38,6 +39,8 @@ module FlexmlsApi
38
39
 
39
40
  def initialize(attributes={})
40
41
  @attributes = {}
42
+ @errors = []
43
+ @changed = []
41
44
  load(attributes)
42
45
  end
43
46
 
@@ -65,7 +68,7 @@ module FlexmlsApi
65
68
  if method_name =~ /(=|\?)$/
66
69
  case $1
67
70
  when "="
68
- attributes[$`] = arguments.first
71
+ write_attribute($`, arguments.first)
69
72
  # TODO figure out a nice way to present setters for the standard fields
70
73
  when "?"
71
74
  if attributes.include?($`)
@@ -79,7 +82,7 @@ module FlexmlsApi
79
82
  super # GTFO
80
83
  end
81
84
  end
82
-
85
+
83
86
  def respond_to?(method_symbol, include_private=false)
84
87
  if super
85
88
  return true
@@ -100,6 +103,16 @@ module FlexmlsApi
100
103
  def parse_id(uri)
101
104
  uri[/\/.*\/(.+)$/, 1]
102
105
  end
106
+
107
+ protected
108
+
109
+ def write_attribute(attribute, value)
110
+ unless attributes[attribute] == value
111
+ attributes[attribute] = value
112
+ @changed << attribute unless @changed.include?(attribute)
113
+ end
114
+ end
115
+
103
116
  end
104
117
  end
105
118
  end
@@ -5,9 +5,13 @@ module FlexmlsApi
5
5
  self.element_name="contacts"
6
6
 
7
7
  def save(arguments={})
8
+ self.errors = [] # clear the errors hash
8
9
  begin
9
10
  return save!(arguments)
10
- rescue NotFound, BadResourceRequest => e
11
+ rescue BadResourceRequest => e
12
+ self.errors << {:code => e.code, :message => e.message}
13
+ FlexmlsApi.logger.error("Failed to save resource #{self}: #{e.message}")
14
+ rescue NotFound => e
11
15
  FlexmlsApi.logger.error("Failed to save resource #{self}: #{e.message}")
12
16
  end
13
17
  false
@@ -6,6 +6,7 @@ module FlexmlsApi
6
6
  attr_accessor :constraints
7
7
  self.element_name="listings"
8
8
  DATA_MASK = "********"
9
+ WRITEABLE_FIELDS = ["ListPrice", "ExpirationDate"]
9
10
 
10
11
  def initialize(attributes={})
11
12
  @photos = []
@@ -58,6 +59,11 @@ module FlexmlsApi
58
59
  collect(connection.get("/company/listings", arguments))
59
60
  end
60
61
 
62
+ def self.nearby(latitude, longitude, arguments={})
63
+ nearby_args = {:Lat => latitude, :Lon => longitude}.merge(arguments)
64
+ collect(connection.get("/listings/nearby", nearby_args))
65
+ end
66
+
61
67
  def tour_of_homes(arguments={})
62
68
  return @tour_of_homes unless @tour_of_homes.nil?
63
69
  @tour_of_homes = TourOfHome.find_by_listing_key(self.Id, arguments)
@@ -104,19 +110,37 @@ module FlexmlsApi
104
110
  end
105
111
 
106
112
  def save(arguments={})
113
+ self.errors = []
107
114
  begin
108
115
  return save!(arguments)
109
- rescue NotFound, BadResourceRequest => e
116
+ rescue BadResourceRequest => e
117
+ self.errors << {:code => e.code, :message => e.message}
118
+ FlexmlsApi.logger.debug("BHDEBUG: #{e.inspect}")
119
+ if e.code == 1053
120
+ @constraints = []
121
+ e.details.each do |detail|
122
+ detail.each_pair do |k,v|
123
+ v.each { |constraint| @constraints << Constraint.new(constraint)}
124
+ end
125
+ end
126
+ end
127
+ FlexmlsApi.logger.error("Failed to save resource #{self}: #{e.message}")
128
+ rescue NotFound => e
110
129
  FlexmlsApi.logger.error("Failed to save resource #{self}: #{e.message}")
111
130
  end
112
131
  false
113
132
  end
114
133
  def save!(arguments={})
115
- results = connection.put "#{self.class.path}/#{self.Id}", {"ListPrice"=> self.ListPrice}, arguments
116
- @contstraints = []
117
- results.details.each do |detail|
118
- detail.each_pair do |k,v|
119
- v.each { |constraint| @constraints << Constraint.new(constraint)}
134
+ writable_changed_keys = changed & WRITEABLE_FIELDS
135
+ if writable_changed_keys.empty?
136
+ FlexmlsApi.logger.warn("No supported listing change detected")
137
+ else
138
+ results = connection.put "#{self.class.path}/#{self.Id}", build_hash(writable_changed_keys), arguments
139
+ @contstraints = []
140
+ results.details.each do |detail|
141
+ detail.each_pair do |k,v|
142
+ v.each { |constraint| @constraints << Constraint.new(constraint)}
143
+ end
120
144
  end
121
145
  end
122
146
  true
@@ -131,6 +155,13 @@ module FlexmlsApi
131
155
  editable
132
156
  end
133
157
 
158
+ def ExpirationDate
159
+ attributes["ExpirationDate"]
160
+ end
161
+ def ExpirationDate=(value)
162
+ write_attribute("ExpirationDate", value)
163
+ end
164
+
134
165
 
135
166
  private
136
167
 
@@ -141,7 +172,7 @@ module FlexmlsApi
141
172
  if method_name =~ /(=|\?)$/
142
173
  case $1
143
174
  when "="
144
- attributes[$`] = arguments.first
175
+ write_attribute($`,arguments.first)
145
176
  # TODO figure out a nice way to present setters for the standard fields
146
177
  when "?"
147
178
  attributes[$`]
@@ -152,6 +183,15 @@ module FlexmlsApi
152
183
  super # GTFO
153
184
  end
154
185
  end
186
+
187
+ def build_hash(keys)
188
+ hash = {}
189
+ keys.each do |key|
190
+ hash[key] = attributes[key]
191
+ end
192
+ hash
193
+ end
194
+
155
195
  end
156
196
  end
157
197
  end
@@ -0,0 +1,21 @@
1
+ module FlexmlsApi
2
+ module Models
3
+ class Message < Base
4
+ self.element_name="messages"
5
+
6
+ def save(arguments={})
7
+ begin
8
+ return save!(arguments)
9
+ rescue NotFound, BadResourceRequest => e
10
+ FlexmlsApi.logger.error("Failed to save resource #{self}: #{e.message}")
11
+ end
12
+ false
13
+ end
14
+ def save!(arguments={})
15
+ results = connection.post self.class.path, {"Messages" => [ attributes ]}, arguments
16
+ true
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -45,7 +45,14 @@ module FlexmlsApi
45
45
  def activate_client_from_config(symbol)
46
46
  FlexmlsApi.logger.debug("Loading multiclient [#{symbol.to_s}] from config")
47
47
  yaml = YamlConfig.build(symbol.to_s)
48
- Client.new(yaml.client_keys)
48
+ if(yaml.oauth2?)
49
+ Client.new(yaml.client_keys.merge({
50
+ :oauth2_provider => Authentication::OAuth2Impl.load_provider(yaml.oauth2_provider, yaml.oauth2_keys),
51
+ :authentication_mode => FlexmlsApi::Authentication::OAuth2,
52
+ }))
53
+ else
54
+ Client.new(yaml.client_keys)
55
+ end
49
56
  end
50
57
 
51
58
  end
@@ -29,12 +29,13 @@ module FlexmlsApi
29
29
  # Errors built from API responses
30
30
  class InvalidResponse < StandardError; end
31
31
  class ClientError < StandardError
32
- attr_reader :code, :status
32
+ attr_reader :code, :status, :details
33
33
  def initialize (options = {})
34
34
  # Support the standard initializer for errors
35
35
  opts = options.is_a?(Hash) ? options : {:message => options.to_s}
36
36
  @code = opts[:code]
37
37
  @status = opts[:status]
38
+ @details = opts[:details]
38
39
  super(opts[:message])
39
40
  end
40
41
 
@@ -66,4 +67,4 @@ module FlexmlsApi
66
67
  end
67
68
  end
68
69
  end
69
- end
70
+ end
@@ -0,0 +1,17 @@
1
+ {
2
+ "D": {
3
+ "Message": "The data you provided failed constraints",
4
+ "Success": false,
5
+ "Details": [{
6
+ "ListPrice": [{
7
+ "RuleValue": 25.0,
8
+ "Value": 500000.0,
9
+ "RuleField": null,
10
+ "RuleFieldValue": null,
11
+ "RuleName": "MaxIncreasePercent"
12
+ }]
13
+ }],
14
+ "Code": 1053
15
+ }
16
+ }
17
+
@@ -0,0 +1,7 @@
1
+ {
2
+ "D": {
3
+ "Message": "The e-mail address 'bhornseth@fbsdata.com' belongs to another prospect",
4
+ "Success": false,
5
+ "Code": 1055
6
+ }
7
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "D": {
3
+ "ExpirationDate": "2011-10-04"
4
+ }
5
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "D": {
3
+ "Messages": [
4
+ {
5
+ "Type": "ShowingRequest",
6
+ "EventDateTime": "2011-09-15T14:00:00",
7
+ "SenderId": "20110112234857732941000000",
8
+ "Subject": "Showing Request For 123 Main St, MLS # 12-345",
9
+ "Body": "A showing is requested for ...",
10
+ "ListingId": "20110112234857732941000000"
11
+ }
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "D": {
3
+ "Messages": [
4
+ { }
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "D": {
3
+ "Messages": [
4
+ {
5
+ "Type": "ShowingRequest",
6
+ "EventDateTime": "2011-09-15T14:00:00",
7
+ "SenderId": "20110112234857732941000000",
8
+ "Subject": "Showing Request For 123 Main St, MLS # 12-345",
9
+ "Body": "A showing is requested for ...",
10
+ "ListingId": "20110112234857732941000000",
11
+ "Recipients": ["20110112234857732941000000","20110092234857738467000000"]
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "D": {
3
+ "Success": true
4
+ }
5
+ }
@@ -176,3 +176,14 @@ describe "password authentication" do
176
176
  subject.authenticate.expires_in.should eq(60)
177
177
  end
178
178
  end
179
+ describe FlexmlsApi::Authentication::OAuth2Impl do
180
+ it "should load a provider" do
181
+ example = "FlexmlsApi::Authentication::OAuth2Impl::PasswordProvider"
182
+ FlexmlsApi::Authentication::OAuth2Impl.load_provider(example,{}).class.to_s.should eq(example)
183
+ prefix = "::#{example}"
184
+ FlexmlsApi::Authentication::OAuth2Impl.load_provider(prefix,{}).class.to_s.should eq(example)
185
+ bad_example = "Derp::Derp::Derp::DerpProvider"
186
+ expect{FlexmlsApi::Authentication::OAuth2Impl.load_provider(bad_example,{}).class.to_s.should eq(bad_example)}.to raise_error(ArgumentError)
187
+ end
188
+
189
+ end
@@ -44,8 +44,9 @@ describe FlexmlsApi::Configuration::YamlConfig, "Yaml Config" do
44
44
  subject.client_id.should eq("developmentid124nj4qu3pua")
45
45
  subject.client_secret.should eq("developmentsecret4orkp29f")
46
46
  subject.endpoint.should eq("http://api.dev.flexmls.com")
47
+ subject.oauth2_provider.should eq("FlexmlsApi::TestOAuth2Provider")
47
48
  subject.name.should eq("test_oauth")
48
- subject.client_keys.keys.should eq([:endpoint])
49
+ subject.client_keys.keys.should eq([:endpoint, :oauth2_provider])
49
50
  subject.oauth2_keys.keys.should =~ [:authorization_uri, :client_id, :access_uri, :client_secret, :redirect_uri]
50
51
  end
51
52
  it "should load a configured api key for production" do
@@ -59,6 +60,7 @@ describe FlexmlsApi::Configuration::YamlConfig, "Yaml Config" do
59
60
  subject.client_id.should eq("production1id124nj4qu3pua")
60
61
  subject.client_secret.should eq("productionsecret4orkp29fv")
61
62
  subject.endpoint.should eq("http://api.flexmls.com")
63
+ subject.oauth2_provider.should eq(subject.class::DEFAULT_OAUTH2_PROVIDER)
62
64
  subject.name.should eq("test_oauth")
63
65
  end
64
66
 
@@ -72,6 +72,18 @@ describe Contact do
72
72
  c.save.should be(false)
73
73
  expect{ c.save! }.to raise_error(FlexmlsApi::ClientError){ |e| e.status.should == 400 }
74
74
  end
75
+
76
+ it "should fail saving and set @errors" do
77
+ stub_api_post("/contacts", 'contacts/new_empty.json') do |request|
78
+ request.to_return(:status => 400, :body => fixture('errors/failure_with_msg.json'))
79
+ end
80
+
81
+ c=Contact.new
82
+ c.errors.length.should eq(0)
83
+ c.save.should be_false
84
+ c.errors.length.should eq(1)
85
+ c.errors.first[:code].should eq(1055)
86
+ end
75
87
 
76
88
  context "on an epic fail" do
77
89
  it "should fail saving and asplode" do
@@ -152,6 +152,12 @@ describe Listing do
152
152
  l.documents.length.should == 0
153
153
  end
154
154
 
155
+ it "should return nearby homes" do
156
+ stub_api_get("/listings/nearby", 'listings/no_subresources.json', {:Lat => "45.45", :Lon => "-93.98"})
157
+ l = Listing.nearby(45.45, -93.98)
158
+ l.length.should == 1
159
+ end
160
+
155
161
  it "should return street address" do
156
162
  @listing.street_address.should eq("100 Someone's St")
157
163
  end
@@ -175,7 +181,7 @@ describe Listing do
175
181
  end
176
182
 
177
183
  context "on save" do
178
- it "should save a listing that has modified" do
184
+ it "should save a listing that has modified ListPrice" do
179
185
  list_id = "20060725224713296297000000"
180
186
  stub_api_get("/listings/#{list_id}", 'listings/no_subresources.json')
181
187
  stub_api_put("/listings/#{list_id}", 'listings/put.json', 'success.json')
@@ -183,6 +189,14 @@ describe Listing do
183
189
  l.ListPrice = 10000.0
184
190
  l.save.should be(true)
185
191
  end
192
+ it "should save a listing that has modified ExpirationDate" do
193
+ list_id = "20060725224713296297000000"
194
+ stub_api_get("/listings/#{list_id}", 'listings/no_subresources.json')
195
+ stub_api_put("/listings/#{list_id}", 'listings/put_expiration_date.json', 'success.json')
196
+ l = Listing.find(list_id)
197
+ l.ExpirationDate = "2011-10-04"
198
+ l.save.should be(true)
199
+ end
186
200
  it "should not save a listing that does not exist" do
187
201
  list_id = "20060725224713296297000000"
188
202
  stub_api_get("/listings/#{list_id}", 'listings/no_subresources.json')
@@ -205,6 +219,22 @@ describe Listing do
205
219
  l.constraints.size.should eq(1)
206
220
  l.constraints.first.RuleName.should eq("MaxValue")
207
221
  end
222
+
223
+ it "should fail saving a listing with constraints and provide the constraints" do
224
+ list_id = "20060725224713296297000000"
225
+ stub_api_get("/listings/#{list_id}", 'listings/no_subresources.json')
226
+ stub_api_put("/listings/#{list_id}", 'listings/put.json') do |request|
227
+ request.to_return(:status => 400, :body => fixture('errors/failure_with_constraint.json'))
228
+ end
229
+
230
+ l = Listing.find(list_id)
231
+ l.ListPrice = 10000.0
232
+ l.save.should be_false
233
+ l.constraints.size.should eq(1)
234
+ l.constraints.first.RuleName.should eq("MaxIncreasePercent")
235
+ l.errors.size.should eq(1)
236
+ end
237
+
208
238
  context "with pagination" do
209
239
  # This is really a bogus call, but we should make sure our pagination collection impl still behaves sanely
210
240
  it "should save a listing with constraints" do
@@ -0,0 +1,44 @@
1
+ require './spec/spec_helper'
2
+
3
+
4
+ describe Message do
5
+ before(:each) do
6
+ stub_auth_request
7
+ end
8
+
9
+ subject do
10
+ m = Message.new
11
+ m.attributes["Type"] = "ShowingRequest"
12
+ m.attributes["EventDateTime"] = "2011-09-15T14:00:00"
13
+ m.attributes["SenderId"] = "20110112234857732941000000"
14
+ m.attributes["Subject"] = "Showing Request For 123 Main St, MLS # 12-345"
15
+ m.attributes["Body"] = "A showing is requested for ..."
16
+ m.attributes["ListingId"] = "20110112234857732941000000"
17
+ m
18
+ end
19
+
20
+ it "should get all my messages"
21
+
22
+ it "should get a single message"
23
+
24
+ it "should save a new message" do
25
+ stub_api_post("/messages", 'messages/new.json', 'messages/post.json')
26
+ subject.save.should be(true)
27
+ end
28
+
29
+ it "should save a new message with recipients" do
30
+ stub_api_post("/messages", 'messages/new_with_recipients.json', 'messages/post.json')
31
+ subject.attributes["Recipients"] = ["20110112234857732941000000","20110092234857738467000000"]
32
+ subject.save.should be(true)
33
+ end
34
+
35
+ it "should fail saving" do
36
+ stub_api_post("/messages", 'messages/new_empty.json') do |request|
37
+ request.to_return(:status => 400, :body => fixture('errors/failure.json'))
38
+ end
39
+ m=subject.class.new
40
+ m.save.should be(false)
41
+ expect{ m.save! }.to raise_error(FlexmlsApi::ClientError){ |e| e.status.should == 400 }
42
+ end
43
+
44
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flexmls_api
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
- - 0
10
- version: 0.7.0
9
+ - 3
10
+ version: 0.7.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brandon Hornseth
@@ -16,60 +16,42 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-10-03 00:00:00 Z
19
+ date: 2012-02-16 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- prerelease: false
23
- type: :runtime
24
- requirement: &id001 !ruby/object:Gem::Requirement
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
23
  none: false
26
24
  requirements:
27
25
  - - ~>
28
26
  - !ruby/object:Gem::Version
29
- hash: 29
27
+ hash: 9
30
28
  segments:
31
29
  - 0
32
30
  - 7
33
- - 15
34
- version: 0.7.15
35
- version_requirements: *id001
36
- name: curb
37
- - !ruby/object:Gem::Dependency
31
+ - 5
32
+ version: 0.7.5
33
+ requirement: *id001
38
34
  prerelease: false
39
- type: :runtime
40
- requirement: &id002 !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- hash: 5
46
- segments:
47
- - 0
48
- - 6
49
- - 1
50
- version: 0.6.1
51
- version_requirements: *id002
52
35
  name: faraday
53
- - !ruby/object:Gem::Dependency
54
- prerelease: false
55
36
  type: :runtime
56
- requirement: &id003 !ruby/object:Gem::Requirement
37
+ - !ruby/object:Gem::Dependency
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
57
39
  none: false
58
40
  requirements:
59
41
  - - ~>
60
42
  - !ruby/object:Gem::Version
61
- hash: 1
43
+ hash: 3
62
44
  segments:
63
45
  - 0
64
- - 6
65
- - 3
66
- version: 0.6.3
67
- version_requirements: *id003
68
- name: faraday_middleware
69
- - !ruby/object:Gem::Dependency
46
+ - 7
47
+ - 0
48
+ version: 0.7.0
49
+ requirement: *id002
70
50
  prerelease: false
51
+ name: faraday_middleware
71
52
  type: :runtime
72
- requirement: &id004 !ruby/object:Gem::Requirement
53
+ - !ruby/object:Gem::Dependency
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
73
55
  none: false
74
56
  requirements:
75
57
  - - ~>
@@ -80,12 +62,12 @@ dependencies:
80
62
  - 0
81
63
  - 0
82
64
  version: 1.0.0
83
- version_requirements: *id004
84
- name: multi_json
85
- - !ruby/object:Gem::Dependency
65
+ requirement: *id003
86
66
  prerelease: false
67
+ name: multi_json
87
68
  type: :runtime
88
- requirement: &id005 !ruby/object:Gem::Requirement
69
+ - !ruby/object:Gem::Dependency
70
+ version_requirements: &id004 !ruby/object:Gem::Requirement
89
71
  none: false
90
72
  requirements:
91
73
  - - ~>
@@ -96,12 +78,12 @@ dependencies:
96
78
  - 5
97
79
  - 1
98
80
  version: 1.5.1
99
- version_requirements: *id005
100
- name: json
101
- - !ruby/object:Gem::Dependency
81
+ requirement: *id004
102
82
  prerelease: false
83
+ name: json
103
84
  type: :runtime
104
- requirement: &id006 !ruby/object:Gem::Requirement
85
+ - !ruby/object:Gem::Dependency
86
+ version_requirements: &id005 !ruby/object:Gem::Requirement
105
87
  none: false
106
88
  requirements:
107
89
  - - ~>
@@ -112,12 +94,12 @@ dependencies:
112
94
  - 8
113
95
  - 2
114
96
  version: 0.8.2
115
- version_requirements: *id006
116
- name: yajl-ruby
117
- - !ruby/object:Gem::Dependency
97
+ requirement: *id005
118
98
  prerelease: false
99
+ name: yajl-ruby
119
100
  type: :runtime
120
- requirement: &id007 !ruby/object:Gem::Requirement
101
+ - !ruby/object:Gem::Dependency
102
+ version_requirements: &id006 !ruby/object:Gem::Requirement
121
103
  none: false
122
104
  requirements:
123
105
  - - ">="
@@ -136,12 +118,12 @@ dependencies:
136
118
  - 0
137
119
  - 0
138
120
  version: 4.0.0
139
- version_requirements: *id007
140
- name: builder
141
- - !ruby/object:Gem::Dependency
121
+ requirement: *id006
142
122
  prerelease: false
123
+ name: builder
143
124
  type: :runtime
144
- requirement: &id008 !ruby/object:Gem::Requirement
125
+ - !ruby/object:Gem::Dependency
126
+ version_requirements: &id007 !ruby/object:Gem::Requirement
145
127
  none: false
146
128
  requirements:
147
129
  - - ~>
@@ -152,12 +134,12 @@ dependencies:
152
134
  - 2
153
135
  - 5
154
136
  version: 2.2.5
155
- version_requirements: *id008
156
- name: addressable
157
- - !ruby/object:Gem::Dependency
137
+ requirement: *id007
158
138
  prerelease: false
139
+ name: addressable
159
140
  type: :runtime
160
- requirement: &id009 !ruby/object:Gem::Requirement
141
+ - !ruby/object:Gem::Dependency
142
+ version_requirements: &id008 !ruby/object:Gem::Requirement
161
143
  none: false
162
144
  requirements:
163
145
  - - ">="
@@ -177,26 +159,28 @@ dependencies:
177
159
  - 0
178
160
  - 0
179
161
  version: 4.0.0
180
- version_requirements: *id009
162
+ requirement: *id008
163
+ prerelease: false
181
164
  name: will_paginate
165
+ type: :runtime
182
166
  - !ruby/object:Gem::Dependency
183
- prerelease: false
184
- type: :development
185
- requirement: &id010 !ruby/object:Gem::Requirement
167
+ version_requirements: &id009 !ruby/object:Gem::Requirement
186
168
  none: false
187
169
  requirements:
188
- - - ">="
170
+ - - ~>
189
171
  - !ruby/object:Gem::Version
190
- hash: 3
172
+ hash: 63
191
173
  segments:
192
174
  - 0
193
- version: "0"
194
- version_requirements: *id010
195
- name: rake
196
- - !ruby/object:Gem::Dependency
175
+ - 9
176
+ - 2
177
+ version: 0.9.2
178
+ requirement: *id009
197
179
  prerelease: false
180
+ name: rake
198
181
  type: :development
199
- requirement: &id011 !ruby/object:Gem::Requirement
182
+ - !ruby/object:Gem::Dependency
183
+ version_requirements: &id010 !ruby/object:Gem::Requirement
200
184
  none: false
201
185
  requirements:
202
186
  - - ~>
@@ -207,22 +191,22 @@ dependencies:
207
191
  - 3
208
192
  - 0
209
193
  version: 2.3.0
210
- version_requirements: *id011
211
- name: rspec
212
- - !ruby/object:Gem::Dependency
194
+ requirement: *id010
213
195
  prerelease: false
196
+ name: rspec
214
197
  type: :development
215
- requirement: &id012 !ruby/object:Gem::Requirement
198
+ - !ruby/object:Gem::Dependency
199
+ version_requirements: &id011 !ruby/object:Gem::Requirement
216
200
  none: false
217
201
  requirements:
218
202
  - - ">="
219
203
  - !ruby/object:Gem::Version
220
- hash: 7
204
+ hash: 11
221
205
  segments:
222
206
  - 1
223
- - 4
207
+ - 7
224
208
  - 0
225
- version: 1.4.0
209
+ version: 1.7.0
226
210
  - - <
227
211
  - !ruby/object:Gem::Version
228
212
  hash: 15
@@ -231,28 +215,28 @@ dependencies:
231
215
  - 0
232
216
  - 0
233
217
  version: 2.0.0
234
- version_requirements: *id012
235
- name: webmock
236
- - !ruby/object:Gem::Dependency
218
+ requirement: *id011
237
219
  prerelease: false
220
+ name: webmock
238
221
  type: :development
239
- requirement: &id013 !ruby/object:Gem::Requirement
222
+ - !ruby/object:Gem::Dependency
223
+ version_requirements: &id012 !ruby/object:Gem::Requirement
240
224
  none: false
241
225
  requirements:
242
226
  - - ~>
243
227
  - !ruby/object:Gem::Version
244
- hash: 23
228
+ hash: 31
245
229
  segments:
246
230
  - 0
247
231
  - 2
248
- - 0
249
- version: 0.2.0
250
- version_requirements: *id013
251
- name: typhoeus
252
- - !ruby/object:Gem::Dependency
232
+ - 4
233
+ version: 0.2.4
234
+ requirement: *id012
253
235
  prerelease: false
236
+ name: typhoeus
254
237
  type: :development
255
- requirement: &id014 !ruby/object:Gem::Requirement
238
+ - !ruby/object:Gem::Dependency
239
+ version_requirements: &id013 !ruby/object:Gem::Requirement
256
240
  none: false
257
241
  requirements:
258
242
  - - ~>
@@ -263,12 +247,12 @@ dependencies:
263
247
  - 6
264
248
  - 3
265
249
  version: 1.6.3
266
- version_requirements: *id014
267
- name: ci_reporter
268
- - !ruby/object:Gem::Dependency
250
+ requirement: *id013
269
251
  prerelease: false
252
+ name: ci_reporter
270
253
  type: :development
271
- requirement: &id015 !ruby/object:Gem::Requirement
254
+ - !ruby/object:Gem::Dependency
255
+ version_requirements: &id014 !ruby/object:Gem::Requirement
272
256
  none: false
273
257
  requirements:
274
258
  - - ~>
@@ -279,24 +263,26 @@ dependencies:
279
263
  - 9
280
264
  - 9
281
265
  version: 0.9.9
282
- version_requirements: *id015
283
- name: rcov
284
- - !ruby/object:Gem::Dependency
266
+ requirement: *id014
285
267
  prerelease: false
268
+ name: rcov
286
269
  type: :development
287
- requirement: &id016 !ruby/object:Gem::Requirement
270
+ - !ruby/object:Gem::Dependency
271
+ version_requirements: &id015 !ruby/object:Gem::Requirement
288
272
  none: false
289
273
  requirements:
290
274
  - - ~>
291
275
  - !ruby/object:Gem::Version
292
- hash: 17
276
+ hash: 29
293
277
  segments:
294
278
  - 0
295
279
  - 2
296
- - 3
297
- version: 0.2.3
298
- version_requirements: *id016
280
+ - 5
281
+ version: 0.2.5
282
+ requirement: *id015
283
+ prerelease: false
299
284
  name: flexmls_gems
285
+ type: :development
300
286
  description: The FlexmlsApi gem handles most of the boilerplate for communicating with the flexmls API rest services, including authentication and request parsing.
301
287
  email: api-support@flexmls.com
302
288
  executables:
@@ -327,6 +313,7 @@ files:
327
313
  - lib/flexmls_api/version.rb
328
314
  - lib/flexmls_api/response.rb
329
315
  - lib/flexmls_api/models.rb
316
+ - lib/flexmls_api/connection.rb
330
317
  - lib/flexmls_api/multi_client.rb
331
318
  - lib/flexmls_api/models/document.rb
332
319
  - lib/flexmls_api/models/finders.rb
@@ -335,6 +322,7 @@ files:
335
322
  - lib/flexmls_api/models/contact.rb
336
323
  - lib/flexmls_api/models/video.rb
337
324
  - lib/flexmls_api/models/idx_link.rb
325
+ - lib/flexmls_api/models/message.rb
338
326
  - lib/flexmls_api/models/system_info.rb
339
327
  - lib/flexmls_api/models/listing.rb
340
328
  - lib/flexmls_api/models/constraint.rb
@@ -366,7 +354,9 @@ files:
366
354
  - script/console
367
355
  - script/example.rb
368
356
  - spec/fixtures/oauth2_error.json
357
+ - spec/fixtures/errors/failure_with_constraint.json
369
358
  - spec/fixtures/errors/expired.json
359
+ - spec/fixtures/errors/failure_with_msg.json
370
360
  - spec/fixtures/errors/failure.json
371
361
  - spec/fixtures/count.json
372
362
  - spec/fixtures/notes/new.json
@@ -404,6 +394,7 @@ files:
404
394
  - spec/fixtures/listing_carts/post.json
405
395
  - spec/fixtures/listing_carts/add_listing.json
406
396
  - spec/fixtures/authentication_failure.json
397
+ - spec/fixtures/listings/put_expiration_date.json
407
398
  - spec/fixtures/listings/no_subresources.json
408
399
  - spec/fixtures/listings/saved_search.json
409
400
  - spec/fixtures/listings/constraints_with_pagination.json
@@ -432,6 +423,10 @@ files:
432
423
  - spec/fixtures/contacts/new_notify.json
433
424
  - spec/fixtures/contacts/new.json
434
425
  - spec/fixtures/contacts/post.json
426
+ - spec/fixtures/messages/new_with_recipients.json
427
+ - spec/fixtures/messages/new_empty.json
428
+ - spec/fixtures/messages/new.json
429
+ - spec/fixtures/messages/post.json
435
430
  - spec/fixtures/logo_fbs.png
436
431
  - spec/unit/flexmls_api_spec.rb
437
432
  - spec/unit/flexmls_api/authentication/base_auth_spec.rb
@@ -450,6 +445,7 @@ files:
450
445
  - spec/unit/flexmls_api/models/video_spec.rb
451
446
  - spec/unit/flexmls_api/models/document_spec.rb
452
447
  - spec/unit/flexmls_api/models/property_types_spec.rb
448
+ - spec/unit/flexmls_api/models/message_spec.rb
453
449
  - spec/unit/flexmls_api/models/listing_spec.rb
454
450
  - spec/unit/flexmls_api/models/shared_listing_spec.rb
455
451
  - spec/unit/flexmls_api/models/note_spec.rb
@@ -491,22 +487,24 @@ required_rubygems_version: !ruby/object:Gem::Requirement
491
487
  requirements:
492
488
  - - ">="
493
489
  - !ruby/object:Gem::Version
494
- hash: 23
490
+ hash: 55
495
491
  segments:
496
492
  - 1
497
- - 3
498
- - 6
499
- version: 1.3.6
493
+ - 8
494
+ - 0
495
+ version: 1.8.0
500
496
  requirements: []
501
497
 
502
498
  rubyforge_project: flexmls_api
503
- rubygems_version: 1.8.10
499
+ rubygems_version: 1.8.15
504
500
  signing_key:
505
501
  specification_version: 3
506
502
  summary: A library for interacting with the flexmls web services.
507
503
  test_files:
508
504
  - spec/fixtures/oauth2_error.json
505
+ - spec/fixtures/errors/failure_with_constraint.json
509
506
  - spec/fixtures/errors/expired.json
507
+ - spec/fixtures/errors/failure_with_msg.json
510
508
  - spec/fixtures/errors/failure.json
511
509
  - spec/fixtures/count.json
512
510
  - spec/fixtures/notes/new.json
@@ -544,6 +542,7 @@ test_files:
544
542
  - spec/fixtures/listing_carts/post.json
545
543
  - spec/fixtures/listing_carts/add_listing.json
546
544
  - spec/fixtures/authentication_failure.json
545
+ - spec/fixtures/listings/put_expiration_date.json
547
546
  - spec/fixtures/listings/no_subresources.json
548
547
  - spec/fixtures/listings/saved_search.json
549
548
  - spec/fixtures/listings/constraints_with_pagination.json
@@ -572,6 +571,10 @@ test_files:
572
571
  - spec/fixtures/contacts/new_notify.json
573
572
  - spec/fixtures/contacts/new.json
574
573
  - spec/fixtures/contacts/post.json
574
+ - spec/fixtures/messages/new_with_recipients.json
575
+ - spec/fixtures/messages/new_empty.json
576
+ - spec/fixtures/messages/new.json
577
+ - spec/fixtures/messages/post.json
575
578
  - spec/fixtures/logo_fbs.png
576
579
  - spec/unit/flexmls_api_spec.rb
577
580
  - spec/unit/flexmls_api/authentication/base_auth_spec.rb
@@ -590,6 +593,7 @@ test_files:
590
593
  - spec/unit/flexmls_api/models/video_spec.rb
591
594
  - spec/unit/flexmls_api/models/document_spec.rb
592
595
  - spec/unit/flexmls_api/models/property_types_spec.rb
596
+ - spec/unit/flexmls_api/models/message_spec.rb
593
597
  - spec/unit/flexmls_api/models/listing_spec.rb
594
598
  - spec/unit/flexmls_api/models/shared_listing_spec.rb
595
599
  - spec/unit/flexmls_api/models/note_spec.rb