apisonator 2.100.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +317 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.base +65 -0
  5. data/Gemfile.lock +319 -0
  6. data/Gemfile.on_prem +1 -0
  7. data/Gemfile.on_prem.lock +297 -0
  8. data/LICENSE +202 -0
  9. data/NOTICE +15 -0
  10. data/README.md +230 -0
  11. data/Rakefile +287 -0
  12. data/apisonator.gemspec +47 -0
  13. data/app/api/api.rb +13 -0
  14. data/app/api/internal/alert_limits.rb +32 -0
  15. data/app/api/internal/application_keys.rb +49 -0
  16. data/app/api/internal/application_referrer_filters.rb +43 -0
  17. data/app/api/internal/applications.rb +77 -0
  18. data/app/api/internal/errors.rb +54 -0
  19. data/app/api/internal/events.rb +42 -0
  20. data/app/api/internal/internal.rb +104 -0
  21. data/app/api/internal/metrics.rb +40 -0
  22. data/app/api/internal/service_tokens.rb +46 -0
  23. data/app/api/internal/services.rb +58 -0
  24. data/app/api/internal/stats.rb +42 -0
  25. data/app/api/internal/usagelimits.rb +62 -0
  26. data/app/api/internal/utilization.rb +23 -0
  27. data/bin/3scale_backend +223 -0
  28. data/bin/3scale_backend_worker +26 -0
  29. data/config.ru +4 -0
  30. data/config/puma.rb +192 -0
  31. data/config/schedule.rb +9 -0
  32. data/ext/mkrf_conf.rb +64 -0
  33. data/lib/3scale/backend.rb +67 -0
  34. data/lib/3scale/backend/alert_limit.rb +56 -0
  35. data/lib/3scale/backend/alerts.rb +137 -0
  36. data/lib/3scale/backend/analytics/kinesis.rb +3 -0
  37. data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
  38. data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
  39. data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
  40. data/lib/3scale/backend/analytics/redshift.rb +3 -0
  41. data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
  42. data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
  43. data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
  44. data/lib/3scale/backend/application.rb +330 -0
  45. data/lib/3scale/backend/application_events.rb +76 -0
  46. data/lib/3scale/backend/background_job.rb +65 -0
  47. data/lib/3scale/backend/configurable.rb +20 -0
  48. data/lib/3scale/backend/configuration.rb +151 -0
  49. data/lib/3scale/backend/configuration/loader.rb +42 -0
  50. data/lib/3scale/backend/constants.rb +19 -0
  51. data/lib/3scale/backend/cors.rb +84 -0
  52. data/lib/3scale/backend/distributed_lock.rb +67 -0
  53. data/lib/3scale/backend/environment.rb +21 -0
  54. data/lib/3scale/backend/error_storage.rb +52 -0
  55. data/lib/3scale/backend/errors.rb +343 -0
  56. data/lib/3scale/backend/event_storage.rb +120 -0
  57. data/lib/3scale/backend/experiment.rb +84 -0
  58. data/lib/3scale/backend/extensions.rb +5 -0
  59. data/lib/3scale/backend/extensions/array.rb +19 -0
  60. data/lib/3scale/backend/extensions/hash.rb +26 -0
  61. data/lib/3scale/backend/extensions/nil_class.rb +13 -0
  62. data/lib/3scale/backend/extensions/redis.rb +44 -0
  63. data/lib/3scale/backend/extensions/string.rb +13 -0
  64. data/lib/3scale/backend/extensions/time.rb +110 -0
  65. data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
  66. data/lib/3scale/backend/job_fetcher.rb +122 -0
  67. data/lib/3scale/backend/listener.rb +728 -0
  68. data/lib/3scale/backend/listener_metrics.rb +99 -0
  69. data/lib/3scale/backend/logging.rb +48 -0
  70. data/lib/3scale/backend/logging/external.rb +44 -0
  71. data/lib/3scale/backend/logging/external/impl.rb +93 -0
  72. data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
  73. data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
  74. data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
  75. data/lib/3scale/backend/logging/external/resque.rb +57 -0
  76. data/lib/3scale/backend/logging/logger.rb +18 -0
  77. data/lib/3scale/backend/logging/middleware.rb +62 -0
  78. data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
  79. data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
  80. data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
  81. data/lib/3scale/backend/logging/worker.rb +107 -0
  82. data/lib/3scale/backend/manifest.rb +80 -0
  83. data/lib/3scale/backend/memoizer.rb +277 -0
  84. data/lib/3scale/backend/metric.rb +275 -0
  85. data/lib/3scale/backend/metric/collection.rb +91 -0
  86. data/lib/3scale/backend/oauth.rb +4 -0
  87. data/lib/3scale/backend/oauth/token.rb +26 -0
  88. data/lib/3scale/backend/oauth/token_key.rb +30 -0
  89. data/lib/3scale/backend/oauth/token_storage.rb +313 -0
  90. data/lib/3scale/backend/oauth/token_value.rb +25 -0
  91. data/lib/3scale/backend/period.rb +3 -0
  92. data/lib/3scale/backend/period/boundary.rb +107 -0
  93. data/lib/3scale/backend/period/cache.rb +28 -0
  94. data/lib/3scale/backend/period/period.rb +402 -0
  95. data/lib/3scale/backend/queue_storage.rb +16 -0
  96. data/lib/3scale/backend/rack.rb +49 -0
  97. data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
  98. data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
  99. data/lib/3scale/backend/rack/prometheus.rb +19 -0
  100. data/lib/3scale/backend/saas.rb +6 -0
  101. data/lib/3scale/backend/saas_analytics.rb +4 -0
  102. data/lib/3scale/backend/server.rb +30 -0
  103. data/lib/3scale/backend/server/falcon.rb +52 -0
  104. data/lib/3scale/backend/server/puma.rb +71 -0
  105. data/lib/3scale/backend/service.rb +317 -0
  106. data/lib/3scale/backend/service_token.rb +97 -0
  107. data/lib/3scale/backend/stats.rb +8 -0
  108. data/lib/3scale/backend/stats/aggregator.rb +170 -0
  109. data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
  110. data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
  111. data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
  112. data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
  113. data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
  114. data/lib/3scale/backend/stats/cleaner.rb +195 -0
  115. data/lib/3scale/backend/stats/codes_commons.rb +14 -0
  116. data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
  117. data/lib/3scale/backend/stats/key_generator.rb +73 -0
  118. data/lib/3scale/backend/stats/keys.rb +104 -0
  119. data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
  120. data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
  121. data/lib/3scale/backend/stats/period_commons.rb +34 -0
  122. data/lib/3scale/backend/stats/stats_parser.rb +141 -0
  123. data/lib/3scale/backend/stats/storage.rb +113 -0
  124. data/lib/3scale/backend/statsd.rb +14 -0
  125. data/lib/3scale/backend/storable.rb +35 -0
  126. data/lib/3scale/backend/storage.rb +40 -0
  127. data/lib/3scale/backend/storage_async.rb +4 -0
  128. data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
  129. data/lib/3scale/backend/storage_async/client.rb +205 -0
  130. data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
  131. data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
  132. data/lib/3scale/backend/storage_helpers.rb +278 -0
  133. data/lib/3scale/backend/storage_key_helpers.rb +9 -0
  134. data/lib/3scale/backend/storage_sync.rb +43 -0
  135. data/lib/3scale/backend/transaction.rb +62 -0
  136. data/lib/3scale/backend/transactor.rb +177 -0
  137. data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
  138. data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
  139. data/lib/3scale/backend/transactor/notify_job.rb +47 -0
  140. data/lib/3scale/backend/transactor/process_job.rb +33 -0
  141. data/lib/3scale/backend/transactor/report_job.rb +84 -0
  142. data/lib/3scale/backend/transactor/status.rb +236 -0
  143. data/lib/3scale/backend/transactor/usage_report.rb +182 -0
  144. data/lib/3scale/backend/usage.rb +63 -0
  145. data/lib/3scale/backend/usage_limit.rb +115 -0
  146. data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
  147. data/lib/3scale/backend/util.rb +17 -0
  148. data/lib/3scale/backend/validators.rb +26 -0
  149. data/lib/3scale/backend/validators/base.rb +36 -0
  150. data/lib/3scale/backend/validators/key.rb +17 -0
  151. data/lib/3scale/backend/validators/limits.rb +57 -0
  152. data/lib/3scale/backend/validators/oauth_key.rb +15 -0
  153. data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
  154. data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
  155. data/lib/3scale/backend/validators/referrer.rb +60 -0
  156. data/lib/3scale/backend/validators/service_state.rb +15 -0
  157. data/lib/3scale/backend/validators/state.rb +15 -0
  158. data/lib/3scale/backend/version.rb +5 -0
  159. data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
  160. data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
  161. data/lib/3scale/backend/worker.rb +87 -0
  162. data/lib/3scale/backend/worker_async.rb +88 -0
  163. data/lib/3scale/backend/worker_metrics.rb +44 -0
  164. data/lib/3scale/backend/worker_sync.rb +32 -0
  165. data/lib/3scale/bundler_shim.rb +17 -0
  166. data/lib/3scale/prometheus_server.rb +10 -0
  167. data/lib/3scale/tasks/connectivity.rake +41 -0
  168. data/lib/3scale/tasks/helpers.rb +3 -0
  169. data/lib/3scale/tasks/helpers/environment.rb +23 -0
  170. data/lib/3scale/tasks/stats.rake +131 -0
  171. data/lib/3scale/tasks/swagger.rake +46 -0
  172. data/licenses.xml +1215 -0
  173. metadata +227 -0
@@ -0,0 +1,25 @@
1
+ # This module encodes values in Redis for our tokens
2
+ module ThreeScale
3
+ module Backend
4
+ module OAuth
5
+ class Token
6
+ module Value
7
+ # Note: this module made more sense when it also supported end-users.
8
+ # Given how simple the module is, we could get rid of it in a future
9
+ # refactor.
10
+
11
+ class << self
12
+ # this method is used when creating tokens
13
+ def for(app_id)
14
+ app_id
15
+ end
16
+
17
+ def from(value)
18
+ value
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ require '3scale/backend/period/boundary'
2
+ require '3scale/backend/period/cache'
3
+ require '3scale/backend/period/period'
@@ -0,0 +1,107 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Period
4
+ module Boundary
5
+ module Methods
6
+ ETERNITY_START = Time.utc(1970, 1, 1).freeze
7
+ private_constant :ETERNITY_START
8
+ ETERNITY_FINISH = Time.utc(9999, 12, 31).freeze
9
+ private_constant :ETERNITY_FINISH
10
+
11
+ def start_of(period, ts)
12
+ send(:"#{period}_start", ts)
13
+ end
14
+
15
+ def end_of(period, ts)
16
+ send(:"#{period}_finish", ts)
17
+ end
18
+
19
+ def second_start(ts)
20
+ Time.utc ts.year, ts.month, ts.day, ts.hour, ts.min, ts.sec
21
+ end
22
+
23
+ def second_finish(ts)
24
+ second_start(ts) + 1
25
+ end
26
+
27
+ def minute_start(ts)
28
+ Time.utc ts.year, ts.month, ts.day, ts.hour, ts.min
29
+ end
30
+
31
+ def minute_finish(ts)
32
+ minute_start(ts) + 60
33
+ end
34
+
35
+ def hour_start(ts)
36
+ Time.utc ts.year, ts.month, ts.day, ts.hour
37
+ end
38
+
39
+ def hour_finish(ts)
40
+ hour_start(ts) + 3600
41
+ end
42
+
43
+ def day_start(ts)
44
+ Time.utc ts.year, ts.month, ts.day
45
+ end
46
+
47
+ def day_finish(ts)
48
+ day_start(ts) + 86400
49
+ end
50
+
51
+ def week_start(ts)
52
+ wday = ts.wday
53
+ days_to_monday = wday != 0 ? wday - 1 : 6
54
+ dayts = ts - days_to_monday * 86400
55
+ Time.utc dayts.year, dayts.month, dayts.day
56
+ end
57
+
58
+ def week_finish(ts)
59
+ wday = ts.wday
60
+ days_to_next_monday = wday != 0 ? 8 - wday : 1
61
+ dayts = ts + days_to_next_monday * 86400
62
+ Time.utc dayts.year, dayts.month, dayts.day
63
+ end
64
+
65
+ def month_start(ts)
66
+ Time.utc ts.year, ts.month, 1
67
+ end
68
+
69
+ def month_finish(ts)
70
+ if ts.month == 12
71
+ year = ts.year + 1
72
+ month = 1
73
+ else
74
+ year = ts.year
75
+ month = ts.month + 1
76
+ end
77
+ Time.utc year, month, 1
78
+ end
79
+
80
+ def year_start(ts)
81
+ Time.utc ts.year, 1, 1
82
+ end
83
+
84
+ def year_finish(ts)
85
+ Time.utc ts.year + 1, 1, 1
86
+ end
87
+
88
+ def eternity_start(_ts)
89
+ ETERNITY_START
90
+ end
91
+
92
+ def eternity_finish(_ts)
93
+ ETERNITY_FINISH
94
+ end
95
+ end
96
+
97
+ class << self
98
+ include Methods
99
+
100
+ def get_callable(period, at)
101
+ method(:"#{period}_#{at}")
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,28 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Period
4
+ # A simple last-used instance cache
5
+ #
6
+ # This is expected to be especially effective for us since most
7
+ # of the time what we look for is start/finish and the exact
8
+ # timestamp does not matter.
9
+ #
10
+ class Cache
11
+ @cache = {}
12
+
13
+ class << self
14
+ def get(granularity, start)
15
+ cached = @cache[granularity]
16
+ if cached && cached.start == start
17
+ cached
18
+ end
19
+ end
20
+
21
+ def set(granularity, obj)
22
+ @cache[granularity] = obj
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,402 @@
1
+ # Periods model
2
+ #
3
+ # This is intended to be used instead of just symbols for identifying period
4
+ # granularities (month, day, etc) and specific time periods that belong to
5
+ # a granularity.
6
+ #
7
+ # The object hierarchy is:
8
+ #
9
+ # - Period: Ancestor Class
10
+ #
11
+ # Non-instanceable class, common ancestor of all classes.
12
+ #
13
+ # - Period::Month, Period::Year, etc: Period Granularity Classes
14
+ #
15
+ # Instanceable classes. They act as _values_ of class Period. That is, when you
16
+ # want to describe a "month" granularity, you can only use one object, the
17
+ # Period::Month class.
18
+ #
19
+ # - Period::Month#0x123, Period::Year#0x456, etc: Specific Period Instances
20
+ #
21
+ # These basically attach a timestamp and extra data (ie. start of the period) to
22
+ # a specific granularity.
23
+ #
24
+ # Inheritance
25
+ # ===========
26
+ #
27
+ # Period::Month#0x123 is a Period::Instance and a Period.
28
+ # Period::Month is a Period::Granularity and a Period.
29
+ #
30
+ # Ordering
31
+ # ========
32
+ #
33
+ # Ordering is available at both usable levels (Period::Month class, that is, the
34
+ # granularity, and Period::Month instance, that is the period).
35
+ # Both behave similarly: Period::Month#0x123 compares to other instances only
36
+ # when they have the same granularity, and then only with the start date.
37
+ # Granularity classes also have order from smaller to bigger granularities.
38
+ # That is: Period::Month < Period::Year is true, and Period::Month#0x123 <
39
+ # Period::Month#0x456 is true if the first starts before.
40
+ #
41
+ # Representation
42
+ # ==============
43
+ #
44
+ # Both granularity classes and period instances represent themselves in String
45
+ # (to_s) and JSON (as_json) with the granularity name as a convenience. Be
46
+ # careful when you #to_s a period instance, since you'll only get the
47
+ # granularity name!
48
+ #
49
+ # The instance can also represent itself as a hash (to_hash), which includes all
50
+ # the relevant information (so it's not _just_ the granularity name, but also
51
+ # period start and finish). Again, the instance uses the granularity to
52
+ # represent itself as String/JSON, not the start/finish/timestamp data.
53
+ #
54
+ # Notes
55
+ # =====
56
+ #
57
+ # Period instances have a start and a finish because Ruby cannot call directly
58
+ # a method named "end".
59
+ #
60
+ # Usage
61
+ # =====
62
+ #
63
+ # You use either "Period(:month)" or "Period[:month]" to refer to the
64
+ # Period::Month singleton class, which is a Period::Granularity.
65
+ # You use either "Period(:month, Time.now.utc)" or "Period[:month,
66
+ # Time.now.utc]" to create an instance of the Period::Month class, which you can
67
+ # later call useful methods on such as #start and #finish (which are always
68
+ # cached), and is a Period::Instance.
69
+ # You can build ranges with period granularities and period instances. And you
70
+ # can also break up a specific period into smaller ones, or ask it to create the
71
+ # list of siblings that would make up a bigger granularity.
72
+ #
73
+ module ThreeScale
74
+ module Backend
75
+ def self.Period(granularity, ts = nil)
76
+ Period[granularity, ts]
77
+ end
78
+
79
+ module Period
80
+ Unknown = Class.new StandardError do
81
+ def initialize(granularity)
82
+ super "unknown period granularity '#{granularity.inspect}'"
83
+ end
84
+ end
85
+
86
+ # This describes how granularities break up into each other in descending
87
+ # order and in absolute terms, that is, in discrete quantities. Most
88
+ # notably, week and eternity don't build greater granularities (ie. you
89
+ # cannot use absolute weeks to precisely define a month), but they can be
90
+ # broken up into discrete smaller granularities, and second is not broken
91
+ # up into smaller granularities, but can build bigger ones.
92
+ #
93
+ # Order is _meaningful_.
94
+ LINKS = {
95
+ second: {
96
+ pred: nil,
97
+ succ: :minute,
98
+ },
99
+ minute: {
100
+ pred: :second,
101
+ succ: :hour,
102
+ },
103
+ hour: {
104
+ pred: :minute,
105
+ succ: :day,
106
+ },
107
+ day: {
108
+ pred: :hour,
109
+ succ: :month,
110
+ },
111
+ week: {
112
+ pred: :day,
113
+ succ: nil,
114
+ },
115
+ month: {
116
+ pred: :day,
117
+ succ: :year,
118
+ },
119
+ year: {
120
+ pred: :month,
121
+ succ: :eternity,
122
+ },
123
+ eternity: {
124
+ pred: :year,
125
+ succ: nil,
126
+ }
127
+ }.freeze
128
+ private_constant :LINKS
129
+
130
+ SYMBOLS = LINKS.keys.freeze
131
+ SYMBOLS_DESC = SYMBOLS.reverse.freeze
132
+
133
+ # shared helpers
134
+ module HelperMethods
135
+ # returns nil if not found
136
+ def get_granularity_class(granularity)
137
+ HASH.fetch granularity do
138
+ if granularity.is_a? Granularity
139
+ granularity
140
+ else
141
+ nil
142
+ end
143
+ end
144
+ end
145
+ private :get_granularity_class
146
+ end
147
+ private_constant :HelperMethods
148
+
149
+ # Marker modules - they allow includers to be queried with #is_a? and
150
+ # synonyms. Period is also a marker module, and it contains no instance
151
+ # methods.
152
+ module Instance
153
+ include Period
154
+ end
155
+
156
+ # Marker module with methods applying to granularities
157
+ module Granularity
158
+ include Period
159
+
160
+ def self.included(base)
161
+ base.include Methods
162
+ end
163
+
164
+ # We want to share these methods between granularity classes.
165
+ module Methods
166
+ include HelperMethods
167
+ include Comparable
168
+
169
+ def <=>(other)
170
+ other = get_granularity_class other
171
+ return nil if other.nil?
172
+ # avoid using #== as this is just a pointer comparison - much like
173
+ # comparing object ids.
174
+ if self.equal? other
175
+ 0
176
+ elsif ALL.index(self) < ALL.index(other)
177
+ -1
178
+ else
179
+ 1
180
+ end
181
+ end
182
+ end
183
+ private_constant :Methods
184
+ end
185
+
186
+ # Period class methods
187
+ module ClassMethods
188
+ include Enumerable
189
+
190
+ def each(&blk)
191
+ ALL.each(&blk)
192
+ end
193
+
194
+ def from(granularity, ts = nil)
195
+ klass = get_granularity_class granularity
196
+ raise Unknown, granularity if klass.nil?
197
+ ts ? klass.new(ts) : klass
198
+ end
199
+
200
+ def instance_periods_for_ts(timestamp)
201
+ Hash.new { |hash, period| hash[period] = period.new(timestamp) }
202
+ end
203
+
204
+ alias_method :[], :from
205
+
206
+ include HelperMethods
207
+ end
208
+ private_constant :ClassMethods
209
+
210
+ class << self
211
+ # We want to override Module#<=> because we have different semantics:
212
+ # we use :<=> for granularity order (vs whether a module contains
213
+ # another one in the default Module method). This is the Period module,
214
+ # but leaving this public could lead to confusion:
215
+ #
216
+ # Period < Period::Month
217
+ #
218
+ # would return a boolean, and make users think there is an ordering
219
+ # relation wrt granularities, which is non-sensical. So we are going to
220
+ # make them private, and if you ever need them you can still use "send".
221
+ private :<=>, *Comparable.instance_methods.select { |m| respond_to? m, true }
222
+
223
+ include ClassMethods
224
+
225
+ private
226
+
227
+ # creates a class for a specific period granularity
228
+ def create_granularity_class(name)
229
+ Class.new do
230
+ include Instance
231
+
232
+ @name = name
233
+ @to_s = name.to_s
234
+
235
+ define_singleton_method :start, &Boundary.get_callable(name, :start)
236
+ define_singleton_method :finish, &Boundary.get_callable(name, :finish)
237
+
238
+ class << self
239
+ include Granularity
240
+
241
+ attr_reader :name, :to_s
242
+
243
+ alias_method :to_sym, :name
244
+
245
+ alias_method :from, :new
246
+ alias_method :[], :new
247
+
248
+ # JSON conversion required by ActiveSupport
249
+ def as_json(_options = nil)
250
+ to_s
251
+ end
252
+
253
+ def remaining(ts = Time.now.utc)
254
+ finish(ts) - ts
255
+ end
256
+
257
+ # methods useful to construct ranges
258
+ def succ
259
+ granularity = LINKS[name][:succ]
260
+ Period[granularity] if granularity
261
+ end
262
+
263
+ def pred
264
+ granularity = LINKS[name][:pred]
265
+ Period[granularity] if granularity
266
+ end
267
+
268
+ def new(timestamp = Time.now)
269
+ timestamp = timestamp.utc
270
+ cached = Period::Cache.get name, start(timestamp)
271
+ if cached
272
+ # cache hit
273
+ cached
274
+ else
275
+ # cache miss
276
+ obj = super timestamp
277
+ Period::Cache.set name, obj
278
+ obj
279
+ end
280
+ end
281
+ end
282
+
283
+ # instance methods
284
+ attr_reader :granularity, :timestamp, :start, :finish
285
+
286
+ def initialize(timestamp)
287
+ @granularity = self.class
288
+ @timestamp = timestamp
289
+ @start = granularity.start(self.timestamp)
290
+ @finish = granularity.finish(self.timestamp)
291
+ end
292
+
293
+ # These are convenience methods to let us print directly the
294
+ # granularity of this period _instead of_ the full data including
295
+ # the "compact" representation of the start date. This is so to
296
+ # avoid having to change many users at this stage, but can be
297
+ # confusing as it makes string representation of the instance the
298
+ # same as the one from the granularity.
299
+ def to_s
300
+ granularity.to_s
301
+ end
302
+
303
+ def to_sym
304
+ granularity.to_sym
305
+ end
306
+
307
+ def to_hash
308
+ {
309
+ granularity: to_s,
310
+ start: start,
311
+ finish: finish,
312
+ }
313
+ end
314
+ alias_method :to_h, :to_hash
315
+
316
+ # always define this to prevent ActiveSupport from "discovering" the
317
+ # wrong methods for JSON-ification, ie. #each and/or #to_h(ash).
318
+ def as_json(options = nil)
319
+ granularity.as_json(options)
320
+ end
321
+
322
+ # Comparing two specific period instances only works if they refer
323
+ # to the same granularity and the same specific period range. It
324
+ # does not matter whether their timestamp is different.
325
+ include Comparable
326
+
327
+ def <=>(o)
328
+ case o
329
+ when Symbol, Granularity
330
+ granularity <=> o
331
+ when Instance
332
+ start <=> o.start if granularity == o.granularity
333
+ else
334
+ nil
335
+ end
336
+ end
337
+
338
+ # case equality - whether a timestamp is included
339
+ def ===(ts)
340
+ ts >= start && ts < finish
341
+ end
342
+
343
+ # break this period down into smaller ones
344
+ def break_down(&blk)
345
+ klass = granularity.pred
346
+ iter = if klass.nil?
347
+ []
348
+ else
349
+ klass.new(start)..klass.new(finish - 1)
350
+ end
351
+ iter.each(&blk)
352
+ end
353
+
354
+ # get the list of periods contained by the enclosing period
355
+ def build_up(&blk)
356
+ klass = granularity.succ
357
+ iter = if klass.nil?
358
+ []
359
+ else
360
+ # #each calls break_down
361
+ klass.new(start)
362
+ end
363
+ iter.each(&blk)
364
+ end
365
+
366
+ # Enumerable is not being included because some external code
367
+ # actually thinks of this as a collection that must be listed
368
+ # instead of just inspecting or printing its value (this is the
369
+ # case with RSpec and ActiveSupport at least).
370
+ alias_method :each, :break_down
371
+
372
+ def remaining(ts)
373
+ finish - ts
374
+ end
375
+
376
+ # Range period iteration
377
+ def succ
378
+ self.class.new(finish)
379
+ end
380
+
381
+ def pred
382
+ self.class.new(start - 1)
383
+ end
384
+ end
385
+ end
386
+ end
387
+
388
+ # All period granularities, sorted asc by duration (as defined in SYMBOLS)
389
+ ALL = SYMBOLS.map do |p|
390
+ name = p.capitalize
391
+ # create Period::Month()
392
+ define_singleton_method name do |*args|
393
+ Period.from p, *args
394
+ end
395
+ const_set(name, create_granularity_class(p).freeze)
396
+ end.freeze
397
+ ALL_DESC = ALL.reverse.freeze
398
+
399
+ HASH = Hash[SYMBOLS.zip ALL].freeze
400
+ end
401
+ end
402
+ end