spark_api 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/History.txt +12 -0
  2. data/README.md +7 -0
  3. data/Rakefile +1 -0
  4. data/VERSION +1 -1
  5. data/lib/spark_api/authentication.rb +0 -3
  6. data/lib/spark_api/authentication/oauth2.rb +1 -1
  7. data/lib/spark_api/configuration.rb +6 -1
  8. data/lib/spark_api/models.rb +6 -2
  9. data/lib/spark_api/models/activity.rb +10 -0
  10. data/lib/spark_api/models/base.rb +25 -5
  11. data/lib/spark_api/models/comment.rb +9 -0
  12. data/lib/spark_api/models/concerns/destroyable.rb +1 -1
  13. data/lib/spark_api/models/concerns/savable.rb +3 -6
  14. data/lib/spark_api/models/contact.rb +38 -1
  15. data/lib/spark_api/models/dirty.rb +1 -1
  16. data/lib/spark_api/models/fields.rb +12 -0
  17. data/lib/spark_api/models/listing_cart.rb +6 -34
  18. data/lib/spark_api/models/portal.rb +37 -0
  19. data/lib/spark_api/models/saved_search.rb +36 -0
  20. data/lib/spark_api/models/vow_account.rb +44 -0
  21. data/lib/spark_api/request.rb +1 -1
  22. data/spec/fixtures/activities/get.json +22 -0
  23. data/spec/fixtures/base.json +2 -2
  24. data/spec/fixtures/comments/get.json +32 -0
  25. data/spec/fixtures/comments/new.json +7 -0
  26. data/spec/fixtures/comments/post.json +19 -0
  27. data/spec/fixtures/contacts/my.json +1 -0
  28. data/spec/fixtures/contacts/vow_accounts/edit.json +5 -0
  29. data/spec/fixtures/contacts/vow_accounts/get.json +15 -0
  30. data/spec/fixtures/contacts/vow_accounts/new.json +12 -0
  31. data/spec/fixtures/contacts/vow_accounts/post.json +10 -0
  32. data/spec/fixtures/fields/order.json +22 -0
  33. data/spec/fixtures/fields/order_a.json +39 -0
  34. data/spec/fixtures/portal/disable.json +5 -0
  35. data/spec/fixtures/portal/enable.json +5 -0
  36. data/spec/fixtures/portal/my.json +15 -0
  37. data/spec/fixtures/portal/my_non_existant.json +6 -0
  38. data/spec/fixtures/portal/new.json +7 -0
  39. data/spec/fixtures/portal/post.json +10 -0
  40. data/spec/fixtures/saved_searches/get.json +4 -1
  41. data/spec/spec_helper.rb +11 -7
  42. data/spec/unit/spark_api/authentication/oauth2_impl/faraday_middleware_spec.rb +2 -2
  43. data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb +1 -2
  44. data/spec/unit/spark_api/authentication/oauth2_spec.rb +3 -4
  45. data/spec/unit/spark_api/models/activity_spec.rb +29 -0
  46. data/spec/unit/spark_api/models/base_spec.rb +23 -0
  47. data/spec/unit/spark_api/models/concerns/destroyable_spec.rb +1 -1
  48. data/spec/unit/spark_api/models/concerns/savable_spec.rb +8 -4
  49. data/spec/unit/spark_api/models/contact_spec.rb +144 -21
  50. data/spec/unit/spark_api/models/fields_spec.rb +56 -0
  51. data/spec/unit/spark_api/models/listing_cart_spec.rb +1 -1
  52. data/spec/unit/spark_api/models/portal_spec.rb +50 -0
  53. data/spec/unit/spark_api/models/saved_search_spec.rb +60 -0
  54. data/spec/unit/spark_api/models/shared_listing_spec.rb +1 -1
  55. data/spec/unit/spark_api/models/vow_account_spec.rb +64 -0
  56. data/spec/unit/spark_api/request_spec.rb +1 -1
  57. data/spec/unit/spark_api_spec.rb +1 -1
  58. metadata +362 -360
  59. data/lib/spark_api/models/subscription.rb +0 -52
  60. data/spec/fixtures/subscriptions/get.json +0 -19
  61. data/spec/fixtures/subscriptions/new.json +0 -13
  62. data/spec/fixtures/subscriptions/post.json +0 -10
  63. data/spec/fixtures/subscriptions/put.json +0 -12
  64. data/spec/fixtures/subscriptions/subscribe.json +0 -5
  65. data/spec/fixtures/subscriptions/update.json +0 -6
  66. data/spec/json_hash_test_support.rb +0 -251
  67. data/spec/json_helper.rb +0 -76
  68. data/spec/mock_helper.rb +0 -132
  69. data/spec/oauth2_helper.rb +0 -70
  70. data/spec/unit/spark_api/models/subscription_spec.rb +0 -106
@@ -1,3 +1,15 @@
1
+ == v1.3.0 2013-01-08
2
+ * yajl is no longer a dependency. To use it, make sure the gem is installed.
3
+ * JRuby support finalized
4
+ * Increased default OAuthSession timeout to 12 hours
5
+ * New API Support:
6
+ * field ordering service
7
+ * attaching/detaching contacts to saved searches,
8
+ * agent portal administration
9
+ * contact commenting
10
+ * contact export, export all
11
+ * contact vow accounts
12
+ * activity stream retrieval
1
13
  == v1.2.0 2012-11-14
2
14
  * SSL verification enabled by default
3
15
  * Sparkbar token access support
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  Spark API
2
2
  =====================
3
+ [![Build Status](https://secure.travis-ci.org/sparkapi/spark_api.png)](http://travis-ci.org/sparkapi/spark_api) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/sparkapi/spark_api)
4
+
3
5
  A Ruby wrapper for the Spark REST API. Loosely based on ActiveResource to provide models to interact with remote services.
4
6
 
5
7
 
@@ -65,6 +67,11 @@ The client also provides ActiveModelesque interface for working with the api res
65
67
  # Top list price: $199999.99
66
68
  puts Account.find(:first, :_filter => "UserType Eq 'Member' And Name Eq 'John*'").Name
67
69
  # John Doe
70
+
71
+
72
+ JSON Parsing
73
+ --------------
74
+ By default, this gem uses the pure ruby json gem for parsing API responses for cross platform compatibility. Projects that include the yajl-ruby gem will see noticeable speed improvements when installed.
68
75
 
69
76
 
70
77
  Authentication Types
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require "rubygems"
2
+
2
3
  require 'rubygems/user_interaction'
3
4
  require 'flexmls_gems/tasks'
4
5
  require 'flexmls_gems/tasks/spec'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.1
1
+ 1.3.0
@@ -1,7 +1,4 @@
1
1
 
2
- require 'openssl'
3
- require 'faraday'
4
- require 'yajl'
5
2
  require 'date'
6
3
 
7
4
  require 'spark_api/authentication/base_auth'
@@ -159,7 +159,7 @@ module SparkApi
159
159
  @scope = options["scope"]
160
160
  @refresh_token = options["refresh_token"]
161
161
  @start_time = options.fetch("start_time", DateTime.now)
162
- @refresh_timeout = options.fetch("refresh_timeout",3600)
162
+ @refresh_timeout = options.fetch("refresh_timeout", 43200)
163
163
  if @start_time.is_a? String
164
164
  @start_time = DateTime.parse(@start_time)
165
165
  end
@@ -1,7 +1,12 @@
1
1
  module SparkApi
2
2
  module Configuration
3
3
 
4
- MultiJson.engine = "yajl"
4
+ begin
5
+ require 'yajl'
6
+ MultiJson.engine = "yajl"
7
+ rescue LoadError => e
8
+ # Using pure ruby JSON parser
9
+ end
5
10
 
6
11
  # valid configuration options
7
12
  VALID_OPTION_KEYS = [:api_key, :api_secret, :api_user, :endpoint,
@@ -6,10 +6,13 @@ require 'spark_api/models/subresource'
6
6
  require 'spark_api/models/concerns'
7
7
 
8
8
  require 'spark_api/models/account'
9
+ require 'spark_api/models/activity'
9
10
  require 'spark_api/models/connect_prefs'
10
11
  require 'spark_api/models/contact'
12
+ require 'spark_api/models/comment'
11
13
  require 'spark_api/models/custom_fields'
12
14
  require 'spark_api/models/document'
15
+ require 'spark_api/models/fields'
13
16
  require 'spark_api/models/idx_link'
14
17
  require 'spark_api/models/listing'
15
18
  require 'spark_api/models/listing_cart'
@@ -19,7 +22,9 @@ require 'spark_api/models/note'
19
22
  require 'spark_api/models/notification'
20
23
  require 'spark_api/models/open_house'
21
24
  require 'spark_api/models/photo'
25
+ require 'spark_api/models/portal'
22
26
  require 'spark_api/models/property_types'
27
+ require 'spark_api/models/rental_calendar'
23
28
  require 'spark_api/models/saved_search'
24
29
  require 'spark_api/models/shared_listing'
25
30
  require 'spark_api/models/standard_fields'
@@ -27,8 +32,7 @@ require 'spark_api/models/system_info'
27
32
  require 'spark_api/models/tour_of_home'
28
33
  require 'spark_api/models/video'
29
34
  require 'spark_api/models/virtual_tour'
30
- require 'spark_api/models/rental_calendar'
31
- require 'spark_api/models/subscription'
35
+ require 'spark_api/models/vow_account'
32
36
 
33
37
  module SparkApi
34
38
  module Models
@@ -0,0 +1,10 @@
1
+ module SparkApi
2
+ module Models
3
+
4
+ class Activity < Base
5
+ extend Finders
6
+ self.element_name = "activities"
7
+ end
8
+
9
+ end
10
+ end
@@ -7,14 +7,13 @@ module SparkApi
7
7
  extend Paginate
8
8
  include Dirty
9
9
 
10
- attr_accessor :attributes, :errors
10
+ attr_accessor :attributes, :errors, :parent
11
11
 
12
12
  # Name of the resource as related to the path name
13
13
  def self.element_name
14
14
  # TODO I'd love to pull in active model at this point to provide default naming
15
15
  @element_name ||= "resource"
16
16
  end
17
-
18
17
  def self.element_name=(name)
19
18
  @element_name = name
20
19
  end
@@ -26,9 +25,25 @@ module SparkApi
26
25
  def self.prefix=(prefix)
27
26
  @prefix = prefix
28
27
  end
28
+
29
+ def resource_uri
30
+ self.ResourceUri.sub(/^\/#{SparkApi.client.version}/, "") if persisted?
31
+ end
32
+
29
33
  def self.path
30
34
  "#{prefix}#{element_name}"
31
35
  end
36
+ def path
37
+ if self.persisted?
38
+ resource_uri.sub(/\/[0-9]{26}$/, "")
39
+ else
40
+ if @parent
41
+ "#{@parent.class.path}/#{@parent.Id}#{self.class.path}"
42
+ else
43
+ self.class.path
44
+ end
45
+ end
46
+ end
32
47
 
33
48
  def self.connection
34
49
  SparkApi.client
@@ -61,6 +76,11 @@ module SparkApi
61
76
  connection.get(path, options.merge({:_pagination=>"count"}))
62
77
  end
63
78
 
79
+ # update/create hash (can be overridden)
80
+ def post_data
81
+ { resource_pluralized => [ attributes ] }
82
+ end
83
+
64
84
  def method_missing(method_symbol, *arguments)
65
85
  method_name = method_symbol.to_s
66
86
 
@@ -82,7 +102,7 @@ module SparkApi
82
102
  end
83
103
  end
84
104
 
85
- def respond_to?(method_symbol, include_private=false)
105
+ def respond_to?(method_symbol)
86
106
  if super
87
107
  return true
88
108
  else
@@ -105,8 +125,8 @@ module SparkApi
105
125
  uri[/\/.*\/(.+)$/, 1]
106
126
  end
107
127
 
108
- def persisted?
109
- !(@attributes['Id'].nil? && @attributes['ResourceUri'].nil?)
128
+ def persisted?;
129
+ !@attributes['Id'].nil? && !@attributes['ResourceUri'].nil?
110
130
  end
111
131
 
112
132
  protected
@@ -0,0 +1,9 @@
1
+ module SparkApi
2
+ module Models
3
+ class Comment < Base
4
+ include Concerns::Savable,
5
+ Concerns::Destroyable
6
+ self.element_name = "comments"
7
+ end
8
+ end
9
+ end
@@ -17,7 +17,7 @@ module SparkApi
17
17
  false
18
18
  end
19
19
  def destroy!(arguments = {})
20
- connection.delete("#{self.class.path}/#{self.Id}", arguments)
20
+ connection.delete(resource_uri, arguments) if persisted?
21
21
  @destroyed = true
22
22
  true
23
23
  end
@@ -21,10 +21,7 @@ module SparkApi
21
21
  end
22
22
 
23
23
  def create!(arguments = {})
24
- results = connection.post self.class.path, {
25
- resource_pluralized => [ attributes ]
26
- }.merge(params_for_save), arguments
27
-
24
+ results = connection.post self.path, post_data.merge(params_for_save), arguments
28
25
  update_resource_identifiers(results.first)
29
26
  reset_dirty
30
27
  params_for_save.clear
@@ -32,8 +29,8 @@ module SparkApi
32
29
  end
33
30
 
34
31
  def update!(arguments = {})
35
- return true unless changed?
36
- connection.put "#{self.class.path}/#{self.Id}", dirty_attributes, arguments
32
+ return true unless changed? && persisted?
33
+ connection.put resource_uri, dirty_attributes, arguments
37
34
  reset_dirty
38
35
  params_for_save.clear
39
36
  true
@@ -6,7 +6,7 @@ module SparkApi
6
6
  Concerns::Destroyable
7
7
 
8
8
  self.element_name="contacts"
9
-
9
+
10
10
  def self.by_tag(tag_name, arguments={})
11
11
  collect(connection.get("#{path}/tags/#{tag_name}", arguments))
12
12
  end
@@ -19,11 +19,48 @@ module SparkApi
19
19
  new(connection.get('/my/contact', arguments).first)
20
20
  end
21
21
 
22
+ def self.export(arguments={})
23
+ collect(connection.get("/contacts/export", arguments))
24
+ end
25
+
26
+ def self.export_all(arguments={})
27
+ collect(connection.get("/contacts/export/all", arguments))
28
+ end
29
+
22
30
  # Notify the agent of contact creation via a Spark notification.
23
31
  def notify?; params_for_save[:Notify] == true end
24
32
  def notify=(notify_me)
25
33
  params_for_save[:Notify] = notify_me
26
34
  end
35
+
36
+ def saved_searches(arguments = {})
37
+ @saved_searches ||= SavedSearch.collect(connection.get("/contacts/#{self.Id}/savedsearches", arguments))
38
+ end
39
+
40
+ def listing_carts(arguments = {})
41
+ @listing_carts ||= ListingCart.collect(connection.get("/contacts/#{self.Id}/listingcarts", arguments))
42
+ end
43
+
44
+ def comments(arguments = {})
45
+ @comments ||= Comment.collect(connection.get("/contacts/#{self.Id}/comments", arguments))
46
+ end
47
+ def comment(body)
48
+ comment = Comment.new({ :Comment => body })
49
+ comment.parent = self
50
+ comment.save
51
+ comment
52
+ end
53
+
54
+ def vow_account(arguments={})
55
+ return @vow_account if @vow_account
56
+ begin
57
+ @vow_account = VowAccount.new(connection.get("/contacts/#{self.Id}/portal", arguments).first)
58
+ @vow_account.parent = self
59
+ @vow_account
60
+ rescue NotFound
61
+ nil
62
+ end
63
+ end
27
64
 
28
65
  end
29
66
  end
@@ -25,7 +25,7 @@ module SparkApi
25
25
 
26
26
  # hash with changed attributes and their new values
27
27
  def dirty_attributes
28
- changed.inject({}) { |h, k| h[k] = attributes[k]; h }
28
+ changed.inject({}) { |h, k| h[k] = attributes[k.to_s]; h }
29
29
  end
30
30
 
31
31
  private
@@ -0,0 +1,12 @@
1
+ module SparkApi
2
+ module Models
3
+ class Fields < Base
4
+ self.element_name="fields"
5
+
6
+ def self.order(property_type=nil, arguments={})
7
+ connection.get("#{self.path}/order#{"/"+property_type unless property_type.nil?}", arguments)
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -2,6 +2,9 @@ module SparkApi
2
2
  module Models
3
3
  class ListingCart < Base
4
4
  extend Finders
5
+ include Concerns::Savable,
6
+ Concerns::Destroyable
7
+
5
8
  self.element_name="listingcarts"
6
9
 
7
10
  def ListingIds=(listing_ids)
@@ -10,19 +13,19 @@ module SparkApi
10
13
  def Name=(name)
11
14
  attributes["Name"] = name
12
15
  end
13
-
16
+
14
17
  def add_listing(listing)
15
18
  id = listing.respond_to?(:Id) ? listing.Id : listing.to_s
16
19
  results = connection.post("#{self.class.path}/#{self.Id}", {"ListingIds" => [ listing ]})
17
20
  self.ListingCount = results.first["ListingCount"]
18
21
  end
19
-
22
+
20
23
  def remove_listing(listing)
21
24
  id = listing.respond_to?(:Id) ? listing.Id : listing.to_s
22
25
  results = connection.delete("#{self.class.path}/#{self.Id}/listings/#{id}")
23
26
  self.ListingCount = results.first["ListingCount"]
24
27
  end
25
-
28
+
26
29
  def self.for(listings,arguments={})
27
30
  keys = Array(listings).map { |l| l.respond_to?(:Id) ? l.Id : l.to_s }
28
31
  collect(connection.get("/#{self.element_name}/for/#{keys.join(",")}", arguments))
@@ -35,37 +38,6 @@ module SparkApi
35
38
  def self.portal(arguments={})
36
39
  collect(connection.get("/#{self.element_name}/portal", arguments))
37
40
  end
38
-
39
- def save(arguments={})
40
- begin
41
- return save!(arguments)
42
- rescue BadResourceRequest => e
43
- rescue NotFound => e
44
- # log and leave
45
- SparkApi.logger.error("Failed to save contact #{self}: #{e.message}")
46
- end
47
- false
48
- end
49
- def save!(arguments={})
50
- attributes['Id'].nil? ? create!(arguments) : update!(arguments)
51
- end
52
-
53
- def delete(args={})
54
- connection.delete("#{self.class.path}/#{self.Id}", args)
55
- end
56
-
57
- private
58
- def create!(arguments={})
59
- results = connection.post self.class.path, {"ListingCarts" => [ attributes ]}, arguments
60
- result = results.first
61
- attributes['ResourceUri'] = result['ResourceUri']
62
- attributes['Id'] = parse_id(result['ResourceUri'])
63
- true
64
- end
65
- def update!(arguments={})
66
- results = connection.put "#{self.class.path}/#{self.Id}", {"ListingCarts" => [ {"ListingIds" => attributes["ListingIds"],"Name" => attributes["Name"]} ] }, arguments
67
- true
68
- end
69
41
 
70
42
  end
71
43
  end
@@ -0,0 +1,37 @@
1
+ module SparkApi
2
+ module Models
3
+
4
+ class Portal < Base
5
+ extend Finders
6
+ include Concerns::Savable
7
+
8
+ self.element_name = "portal"
9
+
10
+ def self.my(arguments = {})
11
+ portal = collect(connection.get("/portal", arguments)).first
12
+ portal = Portal.new if portal.nil?
13
+ portal
14
+ end
15
+
16
+ def enabled?
17
+ @attributes['Enabled'] == true
18
+ end
19
+
20
+ def enable
21
+ attribute_will_change! "Enabled"
22
+ @attributes['Enabled'] = true
23
+ save
24
+ end
25
+
26
+ def disable
27
+ attribute_will_change! "Enabled"
28
+ @attributes['Enabled'] = false
29
+ save
30
+ end
31
+
32
+ def post_data; attributes end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -16,10 +16,46 @@ module SparkApi
16
16
  end
17
17
  end
18
18
 
19
+ # list contacts (private role)
20
+ def contacts
21
+ return [] unless persisted?
22
+ results = connection.get("#{self.class.path}/#{@attributes["Id"]}")
23
+ @attributes['ContactIds'] = results.first['ContactIds']
24
+ end
25
+
26
+ # attach/detach contact (private role)
27
+ [:attach, :detach].each do |action|
28
+ method = (action == :attach ? :put : :delete)
29
+ define_method(action) do |contact|
30
+ return false unless persisted?
31
+ self.errors = []
32
+ contact_id = contact.is_a?(Contact) ? contact.Id : contact
33
+ begin
34
+ connection.send(method, "#{self.class.path}/#{@attributes["Id"]}/contacts/#{contact_id}")
35
+ rescue BadResourceRequest, NotFound => e
36
+ self.errors << { :code => e.code, :message => e.message }
37
+ SparkApi.logger.error("Failed to #{action} contact #{contact}: #{e.message}")
38
+ return false
39
+ end
40
+ update_contacts(action, contact_id)
41
+ true
42
+ end
43
+ end
44
+
19
45
  private
20
46
 
21
47
  def resource_pluralized; "SavedSearches" end
22
48
 
49
+ def update_contacts(method, contact_id)
50
+ @attributes['ContactIds'] = [] if @attributes['ContactIds'].nil?
51
+ case method
52
+ when :attach
53
+ @attributes['ContactIds'] << contact_id
54
+ when :detach
55
+ @attributes['ContactIds'].delete contact_id
56
+ end
57
+ end
58
+
23
59
  end
24
60
 
25
61
  end