actv 1.1.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 (108) hide show
  1. data/.gitignore +30 -0
  2. data/.rspec +2 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +64 -0
  6. data/Gemfile +4 -0
  7. data/Guardfile +20 -0
  8. data/LICENSE +22 -0
  9. data/README.md +63 -0
  10. data/Rakefile +17 -0
  11. data/actv.gemspec +34 -0
  12. data/lib/actv.rb +48 -0
  13. data/lib/actv/address.rb +9 -0
  14. data/lib/actv/article.rb +89 -0
  15. data/lib/actv/article_search_results.rb +15 -0
  16. data/lib/actv/asset.rb +186 -0
  17. data/lib/actv/asset_channel.rb +13 -0
  18. data/lib/actv/asset_component.rb +8 -0
  19. data/lib/actv/asset_description.rb +15 -0
  20. data/lib/actv/asset_description_type.rb +12 -0
  21. data/lib/actv/asset_image.rb +13 -0
  22. data/lib/actv/asset_legacy_data.rb +20 -0
  23. data/lib/actv/asset_price.rb +11 -0
  24. data/lib/actv/asset_seo_url.rb +5 -0
  25. data/lib/actv/asset_status.rb +17 -0
  26. data/lib/actv/asset_tag.rb +9 -0
  27. data/lib/actv/asset_topic.rb +11 -0
  28. data/lib/actv/base.rb +148 -0
  29. data/lib/actv/channel.rb +13 -0
  30. data/lib/actv/client.rb +330 -0
  31. data/lib/actv/configurable.rb +39 -0
  32. data/lib/actv/default.rb +87 -0
  33. data/lib/actv/error.rb +32 -0
  34. data/lib/actv/error/bad_gateway.rb +11 -0
  35. data/lib/actv/error/bad_request.rb +10 -0
  36. data/lib/actv/error/client_error.rb +39 -0
  37. data/lib/actv/error/enhance_your_calm.rb +10 -0
  38. data/lib/actv/error/forbidden.rb +10 -0
  39. data/lib/actv/error/internal_server_error.rb +11 -0
  40. data/lib/actv/error/not_acceptable.rb +10 -0
  41. data/lib/actv/error/not_found.rb +10 -0
  42. data/lib/actv/error/server_error.rb +19 -0
  43. data/lib/actv/error/service_unavailable.rb +11 -0
  44. data/lib/actv/error/unauthorized.rb +10 -0
  45. data/lib/actv/event.rb +213 -0
  46. data/lib/actv/event_result.rb +8 -0
  47. data/lib/actv/event_search_results.rb +11 -0
  48. data/lib/actv/evergreen.rb +53 -0
  49. data/lib/actv/facet.rb +16 -0
  50. data/lib/actv/facet_term.rb +5 -0
  51. data/lib/actv/facet_value.rb +7 -0
  52. data/lib/actv/identity.rb +28 -0
  53. data/lib/actv/interest.rb +39 -0
  54. data/lib/actv/null_object.rb +19 -0
  55. data/lib/actv/phone_number.rb +9 -0
  56. data/lib/actv/place.rb +24 -0
  57. data/lib/actv/popular_interest.rb +18 -0
  58. data/lib/actv/popular_interest_search_results.rb +12 -0
  59. data/lib/actv/request/multipart_with_file.rb +37 -0
  60. data/lib/actv/response/parse_json.rb +29 -0
  61. data/lib/actv/response/raise_client_error.rb +21 -0
  62. data/lib/actv/response/raise_server_error.rb +18 -0
  63. data/lib/actv/search_results.rb +30 -0
  64. data/lib/actv/sub_event.rb +15 -0
  65. data/lib/actv/tag.rb +9 -0
  66. data/lib/actv/topic.rb +9 -0
  67. data/lib/actv/user.rb +38 -0
  68. data/lib/actv/version.rb +3 -0
  69. data/spec/actv/article_search_results_spec.rb +16 -0
  70. data/spec/actv/article_spec.rb +44 -0
  71. data/spec/actv/asset_channel_spec.rb +17 -0
  72. data/spec/actv/asset_description_spec.rb +17 -0
  73. data/spec/actv/asset_image_spec.rb +27 -0
  74. data/spec/actv/asset_price_spec.rb +23 -0
  75. data/spec/actv/asset_spec.rb +172 -0
  76. data/spec/actv/asset_status_spec.rb +24 -0
  77. data/spec/actv/base_spec.rb +51 -0
  78. data/spec/actv/client/articles_spec.rb +130 -0
  79. data/spec/actv/client/assets_spec.rb +87 -0
  80. data/spec/actv/client/event_results_spec.rb +35 -0
  81. data/spec/actv/client/events_spec.rb +99 -0
  82. data/spec/actv/client/search_spec.rb +58 -0
  83. data/spec/actv/client/system_health_spec.rb +16 -0
  84. data/spec/actv/client/users_spec.rb +31 -0
  85. data/spec/actv/client_spec.rb +140 -0
  86. data/spec/actv/event_spec.rb +331 -0
  87. data/spec/actv/evergreen_spec.rb +33 -0
  88. data/spec/actv/identifiable_spec.rb +31 -0
  89. data/spec/actv/null_object_spec.rb +24 -0
  90. data/spec/actv/place_spec.rb +25 -0
  91. data/spec/actv/search_results_spec.rb +44 -0
  92. data/spec/actv/user_spec.rb +25 -0
  93. data/spec/actv_spec.rb +60 -0
  94. data/spec/faraday/response_spec.rb +0 -0
  95. data/spec/fixtures/me.json +21 -0
  96. data/spec/fixtures/system_health.json +1 -0
  97. data/spec/fixtures/valid_article.json +187 -0
  98. data/spec/fixtures/valid_asset.json +185 -0
  99. data/spec/fixtures/valid_event.json +188 -0
  100. data/spec/fixtures/valid_event_results.json +23 -0
  101. data/spec/fixtures/valid_evergreen.json +1 -0
  102. data/spec/fixtures/valid_evergreen_child_1.json +1 -0
  103. data/spec/fixtures/valid_search.json +1282 -0
  104. data/spec/fixtures/valid_search_no_event_results.json +5 -0
  105. data/spec/fixtures/valid_search_no_results.json +9 -0
  106. data/spec/spec_helper.rb +15 -0
  107. data/spec/support/helper.rb +43 -0
  108. metadata +432 -0
@@ -0,0 +1,39 @@
1
+ module ACTV
2
+ module Configurable
3
+
4
+ # Convenience method to allow configuration options to be set in a block
5
+ def configure
6
+ yield self
7
+ self
8
+ end
9
+
10
+ CONFIG_KEYS = [
11
+ :connection_options,
12
+ :endpoint,
13
+ :media_endpoint,
14
+ :middleware,
15
+ :search_endpoint,
16
+ :api_key
17
+ ] unless defined? CONFIG_KEYS
18
+
19
+ attr_accessor *CONFIG_KEYS
20
+
21
+ AUTH_KEYS = [
22
+ :consumer_key,
23
+ :consumer_secret,
24
+ :oauth_token,
25
+ :oauth_token_secret,
26
+ ] unless defined? AUTH_KEYS
27
+
28
+ attr_writer *AUTH_KEYS
29
+
30
+ class << self
31
+
32
+ def keys
33
+ @keys ||= CONFIG_KEYS + AUTH_KEYS
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,87 @@
1
+ require 'faraday'
2
+ require 'actv/configurable'
3
+ require 'actv/request/multipart_with_file'
4
+ require 'actv/response/parse_json'
5
+ require 'actv/response/raise_client_error'
6
+ require 'actv/response/raise_server_error'
7
+ # require 'twitter/response/rate_limit'
8
+ require 'actv/version'
9
+
10
+ module ACTV
11
+ module Default
12
+ class << self
13
+
14
+ def options
15
+ Hash[ACTV::Configurable.keys.map{|key| [key, send(key)]}]
16
+ end
17
+
18
+ # @note This is configurable in case you want to use HTTP instead of HTTPS or use a Active-compatible endpoint.
19
+ # @see http://en.blog.wordpress.com/2009/12/12/twitter-api/
20
+ # @see http://staff.tumblr.com/post/287703110/api
21
+ # @see http://developer.typepad.com/typepad-twitter-api/twitter-api.html
22
+ def endpoint
23
+ @endpoint ||= 'https://api.active.com'
24
+ end
25
+
26
+ def media_endpoint
27
+ @media_endpoint ||= 'https://upload.active.com'
28
+ end
29
+
30
+ def search_endpoint
31
+ @search_endpoint ||= 'https://search.active.com'
32
+ end
33
+
34
+ def connection_options
35
+ @connection_options ||= {
36
+ :headers => {
37
+ :accept => 'application/json',
38
+ :user_agent => "Active Ruby Gem #{ACTV::VERSION}"
39
+ },
40
+ :open_timeout => 5,
41
+ :raw => true,
42
+ :ssl => {:verify => false},
43
+ :timeout => 10,
44
+ }
45
+ end
46
+
47
+ # @note Faraday's middleware stack implementation is comparable to that of Rack middleware. The order of middleware is important: the first middleware on the list wraps all others, while the last middleware is the innermost one.
48
+ # @see https://github.com/technoweenie/faraday#advanced-middleware-usage
49
+ # @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
50
+ def middleware
51
+ @middleware ||= Faraday::Builder.new(
52
+ &Proc.new do |builder|
53
+ builder.use ACTV::Request::MultipartWithFile # Convert file uploads to Faraday::UploadIO objects
54
+ builder.use Faraday::Request::Multipart # Checks for files in the payload
55
+ builder.use Faraday::Request::UrlEncoded # Convert request params as "www-form-urlencoded"
56
+ builder.use ACTV::Response::RaiseClientError # Handle 4xx server responses
57
+ builder.use ACTV::Response::ParseJson # Parse JSON response bodies using MultiJson
58
+ builder.use ACTV::Response::RaiseServerError # Handle 5xx server responses
59
+ # builder.use ACTV::Response::RateLimit # Update RateLimit object
60
+ builder.adapter Faraday.default_adapter # Set Faraday's HTTP adapter
61
+ end
62
+ )
63
+ end
64
+
65
+ def consumer_key
66
+ ENV['ACTV_CONSUMER_KEY']
67
+ end
68
+
69
+ def consumer_secret
70
+ ENV['ACTV_CONSUMER_SECRET']
71
+ end
72
+
73
+ def oauth_token
74
+ ENV['ACTV_OAUTH_TOKEN']
75
+ end
76
+
77
+ def oauth_token_secret
78
+ ENV['ACTV_OAUTH_TOKEN_SECRET']
79
+ end
80
+
81
+ def api_key
82
+ nil
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,32 @@
1
+ module ACTV
2
+ # Custom error class for rescuing from all Twitter errors
3
+ class Error < StandardError
4
+ attr_reader :wrapped_exception
5
+
6
+ def self.errors
7
+ @errors ||= Hash[descendants.map{|klass| [klass.const_get(:HTTP_STATUS_CODE), klass]}]
8
+ end
9
+
10
+ def self.descendants
11
+ ObjectSpace.each_object(::Class).select{|klass| klass < self}
12
+ end
13
+
14
+ # Initializes a new Error object
15
+ #
16
+ # @param exception [Exception, String]
17
+ # @return [Twitter::Error]
18
+ def initialize(exception=$!)
19
+ if exception.respond_to?(:backtrace)
20
+ super(exception.message)
21
+ @wrapped_exception = exception
22
+ else
23
+ super(exception.to_s)
24
+ end
25
+ end
26
+
27
+ def backtrace
28
+ @wrapped_exception ? @wrapped_exception.backtrace : super
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ require 'actv/error/server_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 502
6
+ class BadGateway < ACTV::Error::ServerError
7
+ HTTP_STATUS_CODE = 502
8
+ MESSAGE = "A3PI, or something it depends on (like Asset Service) is down."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'actv/error/client_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 400
6
+ class BadRequest < ACTV::Error::ClientError
7
+ HTTP_STATUS_CODE = 400
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,39 @@
1
+ require 'actv/error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns a 4xx HTTP status code or there's an error in Faraday
6
+ class ClientError < ACTV::Error
7
+
8
+ # Create a new error from an HTTP environment
9
+ #
10
+ # @param body [Hash]
11
+ # @return [ACTV::Error]
12
+ def self.from_response_body(body)
13
+ new(parse_error(body))
14
+ end
15
+
16
+ private
17
+
18
+ def self.parse_error(body)
19
+ if body.nil?
20
+ ''
21
+ elsif body[:error]
22
+ if body[:error].is_a? Hash
23
+ body[:error].fetch(:message, "")
24
+ else
25
+ body[:error]
26
+ end
27
+ elsif body[:errors]
28
+ first = Array(body[:errors]).first
29
+ if first.kind_of?(Hash)
30
+ first[:message].chomp
31
+ else
32
+ first.chomp
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ require 'actv/error/client_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 420
6
+ class EnhanceYourCalm < ACTV::Error::ClientError
7
+ HTTP_STATUS_CODE = 420
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'actv/error/client_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 403
6
+ class Forbidden < ACTV::Error::ClientError
7
+ HTTP_STATUS_CODE = 403
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require 'actv/error/server_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 500
6
+ class InternalServerError < ACTV::Error::ServerError
7
+ HTTP_STATUS_CODE = 500
8
+ MESSAGE = "Something is technically wrong."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'actv/error/client_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 406
6
+ class NotAcceptable < ACTV::Error::ClientError
7
+ HTTP_STATUS_CODE = 406
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'actv/error/client_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 404
6
+ class NotFound < ACTV::Error::ClientError
7
+ HTTP_STATUS_CODE = 404
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ require 'actv/error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns a 5xx HTTP status code
6
+ class ServerError < ACTV::Error
7
+ MESSAGE = "Server Error"
8
+
9
+ # Initializes a new ServerError object
10
+ #
11
+ # @param message [String]
12
+ # @return [Twitter::Error::ServerError]
13
+ def initialize(message=nil)
14
+ super(message || self.class.const_get(:MESSAGE))
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ require 'actv/error/server_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 503
6
+ class ServiceUnavailable < ACTV::Error::ServerError
7
+ HTTP_STATUS_CODE = 503
8
+ MESSAGE = "(__-){ Active is over capacity."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'actv/error/client_error'
2
+
3
+ module ACTV
4
+ class Error
5
+ # Raised when Active returns the HTTP status code 401
6
+ class Unauthorized < ACTV::Error::ClientError
7
+ HTTP_STATUS_CODE = 401
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,213 @@
1
+ require 'actv/asset'
2
+
3
+ module ACTV
4
+ class Event < ACTV::Asset
5
+ attr_reader :salesStartDate, :salesEndDate, :activityStartDate, :activityEndDate
6
+ alias sales_start_date salesStartDate
7
+ alias sales_end_date salesEndDate
8
+ alias activity_start_date activityStartDate
9
+ alias activity_end_date activityEndDate
10
+
11
+ def online_registration_available?
12
+ if is_present?(self.registrationUrlAdr)
13
+ if is_present?(self.legacy_data) && is_present?(self.legacy_data.onlineRegistration)
14
+ self.legacy_data.onlineRegistration.downcase == 'true'
15
+ else
16
+ true
17
+ end
18
+ else
19
+ false
20
+ end
21
+ end
22
+
23
+ def registration_open?
24
+ if online_registration_available?
25
+ is_now_between? authoritative_reg_start_date, authoritative_reg_end_date
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ def registration_closed?
32
+ if online_registration_available?
33
+ is_now_after? authoritative_reg_end_date
34
+ else
35
+ false
36
+ end
37
+ end
38
+
39
+ def registration_not_yet_open?
40
+ if online_registration_available?
41
+ is_now_before? authoritative_reg_start_date
42
+ else
43
+ false
44
+ end
45
+ end
46
+
47
+ def event_ended?
48
+ if is_present? activity_end_date
49
+ is_now_after? "#{activity_end_date.split('T').first}T23:59:59"
50
+ end
51
+ end
52
+
53
+ def registration_opening_soon?(time_in_days=3)
54
+ @reg_open_soon ||= begin
55
+ if online_registration_available?
56
+ if self.sales_start_date
57
+ if now_in_utc >= utc_time(self.sales_start_date) - time_in_days.days and
58
+ now_in_utc < utc_time(self.sales_start_date)
59
+ return true
60
+ end
61
+ end
62
+ end
63
+
64
+ false
65
+ end
66
+ end
67
+
68
+ def registration_closing_soon?(time_in_days=3)
69
+ @reg_closing_soon ||= begin
70
+ if online_registration_available?
71
+ if self.sales_end_date
72
+ if now_in_utc >= utc_time(self.sales_end_date) - time_in_days.days and
73
+ now_in_utc < utc_time(self.end_date)
74
+ return true
75
+ end
76
+ end
77
+ end
78
+
79
+ false
80
+ end
81
+ end
82
+
83
+ def display_close_date
84
+ @display_close_date ||= begin
85
+ val = tag_by_description 'displayclosedate'
86
+ if val
87
+ val.downcase == 'true'
88
+ else
89
+ true
90
+ end
91
+ end
92
+ end
93
+ alias display_close_date? display_close_date
94
+
95
+ ############
96
+
97
+ # Returns the asset's registration open date
98
+ # in UTC. This is pulled from the salesStartDate
99
+ def registration_open_date
100
+ Time.parse "#{authoritative_reg_start_date} UTC"
101
+ end
102
+
103
+ # Returns the asset's registration end date
104
+ # in UTC. This is pulled from the salesEndDate
105
+ def registration_close_date
106
+ Time.parse "#{authoritative_reg_end_date} UTC"
107
+ end
108
+
109
+ # Returns the asset's start date
110
+ # in UTC. This is pulled from the activityStartDate.
111
+ def event_start_date
112
+ Time.parse "#{activity_start_date} #{format_timezone_offset(timezone_offset)}"
113
+ end
114
+
115
+ # Returns the asset's end date
116
+ # in UTC. This is pulled from the activityEndDate.
117
+ def event_end_date
118
+ Time.parse "#{activity_end_date} #{format_timezone_offset(timezone_offset)}"
119
+ end
120
+
121
+ def timezone_offset
122
+ place.timezoneOffset + place.timezoneDST
123
+ end
124
+
125
+ ############
126
+
127
+ def image_url
128
+ defaultImage = 'http://www.active.com/images/events/hotrace.gif'
129
+ image = ''
130
+
131
+ self.assetImages.each do |i|
132
+ if i.imageUrlAdr.downcase != defaultImage
133
+ image = i.imageUrlAdr
134
+ break
135
+ end
136
+ end
137
+
138
+ if image.blank?
139
+ if (self.logoUrlAdr && self.logoUrlAdr != defaultImage && self.logoUrlAdr =~ URI::regexp)
140
+ image = self.logoUrlAdr
141
+ end
142
+ end
143
+ image
144
+ end
145
+
146
+ alias online_registration? online_registration_available?
147
+ alias reg_open? registration_open?
148
+ alias reg_closed? registration_closed?
149
+ alias registration_not_open? registration_not_yet_open?
150
+ alias reg_not_open? registration_not_yet_open?
151
+ alias reg_not_yet_open? registration_not_yet_open?
152
+ alias ended? event_ended?
153
+
154
+ private
155
+
156
+ # EG: -7 => "-0700"
157
+ def format_timezone_offset(offset)
158
+ (offset < 0 ? "-" : "") << offset.abs.to_s.rjust(2,'0') << '00'
159
+ end
160
+
161
+ def authoritative_reg_end_date
162
+ if is_present? sales_end_date
163
+ sales_end_date
164
+ elsif is_present? activity_end_date
165
+ "#{activity_end_date.split('T').first}T23:59:59"
166
+ elsif is_present? activity_start_date
167
+ activity_start_date
168
+ else
169
+ "2100-12-31T23:59:59"
170
+ end
171
+ end
172
+
173
+ def authoritative_reg_start_date
174
+ if is_present? sales_start_date
175
+ sales_start_date
176
+ else
177
+ "1970-01-01T00:00:00"
178
+ end
179
+ end
180
+
181
+ def is_now_before? date_string
182
+ now_in_utc < utc_time(date_string)
183
+ end
184
+
185
+ def is_now_after? date_string
186
+ !is_now_before? date_string
187
+ end
188
+
189
+ def is_now_between? start_date_string, end_date_string
190
+ utc_time(start_date_string) < now_in_utc && now_in_utc < utc_time(end_date_string)
191
+ end
192
+
193
+ def is_present? obj
194
+ !is_empty? obj
195
+ end
196
+
197
+ def is_empty? obj
198
+ obj.respond_to?(:empty?) ? obj.empty? : !obj
199
+ end
200
+
201
+ def now_in_utc
202
+ Time.now.utc
203
+ end
204
+
205
+ def utc_time(time_string)
206
+ return nil if time_string.nil? or time_string.empty?
207
+ return Time.parse(time_string).utc
208
+ end
209
+
210
+ end
211
+ end
212
+
213
+