simplyrets 0.1

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.
@@ -0,0 +1,85 @@
1
+
2
+ require 'monkey'
3
+ require 'simplyrets/configuration'
4
+ require 'simplyrets/request'
5
+ require 'simplyrets/response'
6
+ require 'simplyrets/version'
7
+ require 'logger'
8
+
9
+ module SimplyRets
10
+
11
+ class << self
12
+ attr_accessor :logger
13
+
14
+ # A SimplyRets configuration object. Must act like a hash and return sensible
15
+ # values for all SimplyRets configuration options. See SimplyRets::Configuration.
16
+ attr_accessor :configuration
17
+
18
+ attr_accessor :resources
19
+
20
+ # Call this method to modify defaults in your initializers.
21
+ #
22
+ # @example
23
+ # SimplyRets.configure do |config|
24
+ # config.api_key = '1234567890abcdef' # required
25
+ # config.username = 'wordlover' # optional, but needed for user-related functions
26
+ # config.password = 'i<3words' # optional, but needed for user-related functions
27
+ # config.format = 'json' # optional, defaults to 'json'
28
+ # end
29
+ #
30
+ def configure
31
+ self.configuration ||= Configuration.new
32
+ yield(configuration) if block_given?
33
+
34
+ # Configure logger. Default to use Rails
35
+ self.logger ||= configuration.logger || (defined?(Rails) ? Rails.logger : Logger.new(STDOUT))
36
+
37
+ # remove :// from scheme
38
+ configuration.scheme.sub!(/:\/\//, '')
39
+
40
+ # remove http(s):// and anything after a slash
41
+ configuration.host.sub!(/https?:\/\//, '')
42
+ configuration.host = configuration.host.split('/').first
43
+
44
+ # Add leading and trailing slashes to base_path
45
+ configuration.base_path = "/#{configuration.base_path}".gsub(/\/+/, '/')
46
+ configuration.base_path = "" if configuration.base_path == "/"
47
+ end
48
+
49
+ def authenticated?
50
+ SimplyRets.configuration.auth_token.present?
51
+ end
52
+
53
+ def de_authenticate
54
+ SimplyRets.configuration.auth_token = nil
55
+ end
56
+
57
+ def authenticate
58
+ return if SimplyRets.authenticated?
59
+
60
+ if SimplyRets.configuration.username.blank? || SimplyRets.configuration.password.blank?
61
+ raise ClientError, "Username and password are required to authenticate."
62
+ end
63
+
64
+ request = SimplyRets::Request.new(
65
+ :get,
66
+ "account/authenticate/{username}",
67
+ :params => {
68
+ :username => SimplyRets.configuration.username,
69
+ :password => SimplyRets.configuration.password
70
+ }
71
+ )
72
+
73
+ response_body = request.response.body
74
+ SimplyRets.configuration.auth_token = response_body['token']
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ class ServerError < StandardError
82
+ end
83
+
84
+ class ClientError < StandardError
85
+ end
@@ -0,0 +1,25 @@
1
+ module SimplyRets
2
+
3
+ class Configuration
4
+ require 'simplyrets/version'
5
+
6
+ attr_accessor :format, :api_key, :username, :password,
7
+ :auth_token, :scheme, :host, :base_path,
8
+ :user_agent, :logger, :inject_format,
9
+ :force_ending_format, :camelize_params
10
+
11
+ # Defaults go in here..
12
+ def initialize
13
+ @format = 'json'
14
+ @scheme = 'https'
15
+ @host = 'api.simplyrets.com'
16
+ @base_path = '/'
17
+ @user_agent = "ruby-#{SimplyRets::VERSION}"
18
+ @inject_format = false
19
+ @force_ending_format = false
20
+ @camelize_params = true
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,205 @@
1
+ module SimplyRets
2
+
3
+ class Request
4
+ require 'uri'
5
+ require 'addressable/uri'
6
+ require 'typhoeus'
7
+ require 'base64'
8
+ require "simplyrets/version"
9
+
10
+ attr_accessor :host, :path, :format, :params, :body, :http_method, :headers
11
+
12
+
13
+ # All requests must have an HTTP method and a path
14
+ # Optionals parameters are :params, :headers, :body, :format, :host
15
+ #
16
+ def initialize(http_method, path, attributes={})
17
+ attributes[:format] ||= SimplyRets.configuration.format
18
+ attributes[:params] ||= {}
19
+
20
+ # Set default headers
21
+ default_headers = {
22
+ 'Content-Type' => "application/#{attributes[:format].downcase}",
23
+ :api_key => SimplyRets.configuration.api_key
24
+ }
25
+
26
+ # api_key from headers hash trumps the default, even if its value is blank
27
+ if attributes[:headers].present? && attributes[:headers].has_key?(:api_key)
28
+ default_headers.delete(:api_key)
29
+ end
30
+
31
+ # api_key from params hash trumps all others (headers and default_headers)
32
+ if attributes[:params].present? && attributes[:params].has_key?(:api_key)
33
+ default_headers.delete(:api_key)
34
+ attributes[:headers].delete(:api_key) if attributes[:headers].present?
35
+ end
36
+
37
+ # Merge argument headers into defaults
38
+ attributes[:headers] = default_headers.merge(attributes[:headers] || {})
39
+
40
+ # Stick in the auth header
41
+ auth = SimplyRets.configuration.username + ":" + SimplyRets.configuration.password
42
+ attributes[:headers].merge!({"Authorization" =>
43
+ ("Basic " + Base64.encode64(auth))})
44
+
45
+ self.http_method = http_method.to_sym
46
+ self.path = path
47
+ attributes.each do |name, value|
48
+ send("#{name.to_s.underscore.to_sym}=", value)
49
+ end
50
+ end
51
+
52
+ # Construct a base URL
53
+ #
54
+ def url(options = {})
55
+ u = Addressable::URI.new(
56
+ :scheme => SimplyRets.configuration.scheme,
57
+ :host => SimplyRets.configuration.host,
58
+ :path => self.interpreted_path,
59
+ :query => self.query_string.sub(/\?/, '')
60
+ ).to_s
61
+
62
+ # Drop trailing question mark, if present
63
+ u.sub! /\?$/, ''
64
+
65
+ u
66
+ end
67
+
68
+ # Iterate over the params hash, injecting any path values into the path string
69
+ #
70
+ # NOTE: This is currently not supported in the SimplyRETS API. Content
71
+ # formatting is handled via the accept header.
72
+ #
73
+ # e.g. /properties.{format}/{word}/entries => /word.json/cat/entries
74
+ def interpreted_path
75
+ p = self.path.dup
76
+
77
+ # Fill in the path params
78
+ self.params.each_pair do |key, value|
79
+ p = p.gsub("{#{key}}", value.to_s)
80
+ end
81
+
82
+ # Stick a .{format} placeholder into the path if there isn't
83
+ # one already or an actual format like json or xml
84
+ # e.g. /properties/mlsId => /properties.{format}/mlsId
85
+ if SimplyRets.configuration.inject_format
86
+ unless ['.json', '.xml', '{format}'].any? {|s| p.downcase.include? s }
87
+ p = p.sub(/^(\/?\w+)/, "\\1.#{format}")
88
+ end
89
+ end
90
+
91
+ # Stick a .{format} placeholder on the end of the path if there
92
+ # isn't one already or an actual format like json or xml
93
+ #
94
+ # NOTE: This is currently not supported in the SimplyRETS
95
+ # API. Content formatting is handled via the accept header.
96
+ #
97
+ # e.g. /properties/blah => /properties/blah.{format}
98
+ if SimplyRets.configuration.force_ending_format
99
+ unless ['.json', '.xml', '{format}'].any? {|s| p.downcase.include? s }
100
+ p = "#{p}.#{format}"
101
+ end
102
+ end
103
+
104
+ p = p.sub("{format}", self.format.to_s)
105
+
106
+ URI.encode [SimplyRets.configuration.base_path, p].join("/").gsub(/\/+/, '/')
107
+ end
108
+
109
+ # Massage the request body into a state of readiness If body is a
110
+ # hash, camelize all keys then convert to a json string
111
+ def body=(value)
112
+ if value.is_a?(Hash)
113
+ value = value.inject({}) do |memo, (k,v)|
114
+ memo[k.to_s.camelize(:lower).to_sym] = v
115
+ memo
116
+ end
117
+ end
118
+ @body = value
119
+ end
120
+
121
+ # If body is an object, JSONify it before making the actual request.
122
+ #
123
+ def outgoing_body
124
+ body.is_a?(String) ? body : body.to_json
125
+ end
126
+
127
+ # Construct a query string from the query-string-type params
128
+ def query_string
129
+
130
+ # Iterate over all params,
131
+ # .. removing the ones that are part of the path itself.
132
+ # .. stringifying values so Addressable doesn't blow up.
133
+ query_values = {}
134
+ self.params.each_pair do |key, value|
135
+ next if self.path.include? "{#{key}}" # skip path params
136
+ next if value.blank? && value.class != FalseClass # skip empties
137
+ if SimplyRets.configuration.camelize_params
138
+ # api_key is not a camelCased param
139
+ #
140
+ # NOTE: api_key param is not currently used by SimplyRETS API
141
+ key = key.to_s.camelize(:lower).to_sym unless key.to_sym == :api_key
142
+ end
143
+ query_values[key] = value
144
+ end
145
+
146
+ # We don't want to end up with '?' as our query string
147
+ # if there aren't really any params
148
+ return "" if query_values.blank?
149
+
150
+ # Addressable requires query_values to be set after initialization..
151
+ qs = Addressable::URI.new
152
+ qs.query_values = query_values
153
+ qs.to_s
154
+ end
155
+
156
+ def make
157
+ logger = Logger.new STDOUT
158
+ logger.debug self.url
159
+ response = case self.http_method.to_sym
160
+ when :get,:GET
161
+ Typhoeus::Request.get(
162
+ self.url,
163
+ :headers => self.headers.stringify_keys,
164
+ )
165
+
166
+ when :post,:POST
167
+ Typhoeus::Request.post(
168
+ self.url,
169
+ :body => self.outgoing_body,
170
+ :headers => self.headers.stringify_keys,
171
+ )
172
+
173
+ when :put,:PUT
174
+ Typhoeus::Request.put(
175
+ self.url,
176
+ :body => self.outgoing_body,
177
+ :headers => self.headers.stringify_keys,
178
+ )
179
+
180
+ when :delete,:DELETE
181
+ Typhoeus::Request.delete(
182
+ self.url,
183
+ :body => self.outgoing_body,
184
+ :headers => self.headers.stringify_keys,
185
+ )
186
+ end
187
+ Response.new(response)
188
+ end
189
+
190
+ def response
191
+ self.make
192
+ end
193
+
194
+ def response_code_pretty
195
+ return unless @response.present?
196
+ @response.code.to_s
197
+ end
198
+
199
+ def response_headers_pretty
200
+ return unless @response.present?
201
+ @response.headers.gsub(/\n/, '<br/>') # <- This is for Typhoeus
202
+ end
203
+
204
+ end
205
+ end
@@ -0,0 +1,70 @@
1
+ module SimplyRets
2
+
3
+ class Response
4
+ require 'json'
5
+
6
+ attr_accessor :raw
7
+
8
+ def initialize(raw)
9
+ self.raw = raw
10
+
11
+ case self.code
12
+ when 500..510 then raise(ServerError, self.error_message)
13
+ when 299..426 then raise(ClientError, self.error_message)
14
+ end
15
+ end
16
+
17
+ def code
18
+ raw.code
19
+ end
20
+
21
+ # Account for error messages that take different forms...
22
+ def error_message
23
+ body['message']
24
+ rescue
25
+ body
26
+ end
27
+
28
+ # If body is JSON, parse it
29
+ # Otherwise return raw string
30
+ def body
31
+ JSON.parse raw.body
32
+ rescue
33
+ raw.body
34
+ end
35
+
36
+ # `headers_hash` is a Typhoeus-specific extension of Hash,
37
+ # so simplify it back into a regular old Hash.
38
+ def headers
39
+ h = {}
40
+ raw.headers_hash.each {|k,v| h[k] = v }
41
+ h
42
+ end
43
+
44
+ # Extract the response format from the header hash
45
+ # e.g. {'Content-Type' => 'application/json'}
46
+ def format
47
+ headers['Content-Type'].split("/").last.downcase
48
+ end
49
+
50
+ def json?
51
+ format == 'json'
52
+ end
53
+
54
+ def xml?
55
+ format == 'xml'
56
+ end
57
+
58
+ def pretty_body
59
+ return unless body.present?
60
+ case format
61
+ when 'json' then JSON.pretty_generate(body).gsub(/\n/, '<br/>')
62
+ end
63
+ end
64
+
65
+ def pretty_headers
66
+ JSON.pretty_generate(headers).gsub(/\n/, '<br/>')
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,4 @@
1
+ module SimplyRets
2
+ VERSION = "0.1"
3
+ end
4
+
data/models/agent.rb ADDED
@@ -0,0 +1,39 @@
1
+ class Agent
2
+ attr_accessor :last_name, :contact, :first_name, :id
3
+
4
+ # :internal => :external
5
+ def self.attribute_map
6
+ {
7
+ :last_name => :lastName,
8
+ :contact => :contact,
9
+ :first_name => :firstName,
10
+ :id => :id
11
+
12
+ }
13
+ end
14
+
15
+ def initialize(attributes = {})
16
+ return if attributes.empty?
17
+ # Morph attribute keys into undescored rubyish style
18
+ if self.class.attribute_map[:"last_name"]
19
+ @last_name = attributes["lastName"]
20
+ end
21
+ if self.class.attribute_map[:"contact"]
22
+ @contact = attributes["contact"]
23
+ end
24
+ if self.class.attribute_map[:"first_name"]
25
+ @first_name = attributes["firstName"]
26
+ end
27
+ if self.class.attribute_map[:"id"]
28
+ @id = attributes["id"]
29
+ end
30
+ end
31
+
32
+ def to_body
33
+ body = {}
34
+ self.class.attribute_map.each_pair do |key, value|
35
+ body[value] = self.send(key) unless self.send(key).nil?
36
+ end
37
+ body
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ class ContactInformation
2
+ attr_accessor :office, :cell, :full
3
+
4
+ # :internal => :external
5
+ def self.attribute_map
6
+ {
7
+ :office => :office,
8
+ :cell => :cell,
9
+ :full => :full
10
+
11
+ }
12
+ end
13
+
14
+ def initialize(attributes = {})
15
+ return if attributes.empty?
16
+ # Morph attribute keys into undescored rubyish style
17
+ if self.class.attribute_map[:"office"]
18
+ @office = attributes["office"]
19
+ end
20
+ if self.class.attribute_map[:"cell"]
21
+ @cell = attributes["cell"]
22
+ end
23
+ if self.class.attribute_map[:"full"]
24
+ @full = attributes["full"]
25
+ end
26
+
27
+ end
28
+
29
+ def to_body
30
+ body = {}
31
+ self.class.attribute_map.each_pair do |key, value|
32
+ body[value] = self.send(key) unless self.send(key).nil?
33
+ end
34
+ body
35
+ end
36
+ end