actv 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+