spark_api 1.2.1 → 1.3.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.
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