apisonator 2.100.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 (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