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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +317 -0
- data/Gemfile +11 -0
- data/Gemfile.base +65 -0
- data/Gemfile.lock +319 -0
- data/Gemfile.on_prem +1 -0
- data/Gemfile.on_prem.lock +297 -0
- data/LICENSE +202 -0
- data/NOTICE +15 -0
- data/README.md +230 -0
- data/Rakefile +287 -0
- data/apisonator.gemspec +47 -0
- data/app/api/api.rb +13 -0
- data/app/api/internal/alert_limits.rb +32 -0
- data/app/api/internal/application_keys.rb +49 -0
- data/app/api/internal/application_referrer_filters.rb +43 -0
- data/app/api/internal/applications.rb +77 -0
- data/app/api/internal/errors.rb +54 -0
- data/app/api/internal/events.rb +42 -0
- data/app/api/internal/internal.rb +104 -0
- data/app/api/internal/metrics.rb +40 -0
- data/app/api/internal/service_tokens.rb +46 -0
- data/app/api/internal/services.rb +58 -0
- data/app/api/internal/stats.rb +42 -0
- data/app/api/internal/usagelimits.rb +62 -0
- data/app/api/internal/utilization.rb +23 -0
- data/bin/3scale_backend +223 -0
- data/bin/3scale_backend_worker +26 -0
- data/config.ru +4 -0
- data/config/puma.rb +192 -0
- data/config/schedule.rb +9 -0
- data/ext/mkrf_conf.rb +64 -0
- data/lib/3scale/backend.rb +67 -0
- data/lib/3scale/backend/alert_limit.rb +56 -0
- data/lib/3scale/backend/alerts.rb +137 -0
- data/lib/3scale/backend/analytics/kinesis.rb +3 -0
- data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
- data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
- data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
- data/lib/3scale/backend/analytics/redshift.rb +3 -0
- data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
- data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
- data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
- data/lib/3scale/backend/application.rb +330 -0
- data/lib/3scale/backend/application_events.rb +76 -0
- data/lib/3scale/backend/background_job.rb +65 -0
- data/lib/3scale/backend/configurable.rb +20 -0
- data/lib/3scale/backend/configuration.rb +151 -0
- data/lib/3scale/backend/configuration/loader.rb +42 -0
- data/lib/3scale/backend/constants.rb +19 -0
- data/lib/3scale/backend/cors.rb +84 -0
- data/lib/3scale/backend/distributed_lock.rb +67 -0
- data/lib/3scale/backend/environment.rb +21 -0
- data/lib/3scale/backend/error_storage.rb +52 -0
- data/lib/3scale/backend/errors.rb +343 -0
- data/lib/3scale/backend/event_storage.rb +120 -0
- data/lib/3scale/backend/experiment.rb +84 -0
- data/lib/3scale/backend/extensions.rb +5 -0
- data/lib/3scale/backend/extensions/array.rb +19 -0
- data/lib/3scale/backend/extensions/hash.rb +26 -0
- data/lib/3scale/backend/extensions/nil_class.rb +13 -0
- data/lib/3scale/backend/extensions/redis.rb +44 -0
- data/lib/3scale/backend/extensions/string.rb +13 -0
- data/lib/3scale/backend/extensions/time.rb +110 -0
- data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
- data/lib/3scale/backend/job_fetcher.rb +122 -0
- data/lib/3scale/backend/listener.rb +728 -0
- data/lib/3scale/backend/listener_metrics.rb +99 -0
- data/lib/3scale/backend/logging.rb +48 -0
- data/lib/3scale/backend/logging/external.rb +44 -0
- data/lib/3scale/backend/logging/external/impl.rb +93 -0
- data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
- data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
- data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
- data/lib/3scale/backend/logging/external/resque.rb +57 -0
- data/lib/3scale/backend/logging/logger.rb +18 -0
- data/lib/3scale/backend/logging/middleware.rb +62 -0
- data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
- data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
- data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
- data/lib/3scale/backend/logging/worker.rb +107 -0
- data/lib/3scale/backend/manifest.rb +80 -0
- data/lib/3scale/backend/memoizer.rb +277 -0
- data/lib/3scale/backend/metric.rb +275 -0
- data/lib/3scale/backend/metric/collection.rb +91 -0
- data/lib/3scale/backend/oauth.rb +4 -0
- data/lib/3scale/backend/oauth/token.rb +26 -0
- data/lib/3scale/backend/oauth/token_key.rb +30 -0
- data/lib/3scale/backend/oauth/token_storage.rb +313 -0
- data/lib/3scale/backend/oauth/token_value.rb +25 -0
- data/lib/3scale/backend/period.rb +3 -0
- data/lib/3scale/backend/period/boundary.rb +107 -0
- data/lib/3scale/backend/period/cache.rb +28 -0
- data/lib/3scale/backend/period/period.rb +402 -0
- data/lib/3scale/backend/queue_storage.rb +16 -0
- data/lib/3scale/backend/rack.rb +49 -0
- data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
- data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
- data/lib/3scale/backend/rack/prometheus.rb +19 -0
- data/lib/3scale/backend/saas.rb +6 -0
- data/lib/3scale/backend/saas_analytics.rb +4 -0
- data/lib/3scale/backend/server.rb +30 -0
- data/lib/3scale/backend/server/falcon.rb +52 -0
- data/lib/3scale/backend/server/puma.rb +71 -0
- data/lib/3scale/backend/service.rb +317 -0
- data/lib/3scale/backend/service_token.rb +97 -0
- data/lib/3scale/backend/stats.rb +8 -0
- data/lib/3scale/backend/stats/aggregator.rb +170 -0
- data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
- data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
- data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
- data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
- data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
- data/lib/3scale/backend/stats/cleaner.rb +195 -0
- data/lib/3scale/backend/stats/codes_commons.rb +14 -0
- data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
- data/lib/3scale/backend/stats/key_generator.rb +73 -0
- data/lib/3scale/backend/stats/keys.rb +104 -0
- data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
- data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
- data/lib/3scale/backend/stats/period_commons.rb +34 -0
- data/lib/3scale/backend/stats/stats_parser.rb +141 -0
- data/lib/3scale/backend/stats/storage.rb +113 -0
- data/lib/3scale/backend/statsd.rb +14 -0
- data/lib/3scale/backend/storable.rb +35 -0
- data/lib/3scale/backend/storage.rb +40 -0
- data/lib/3scale/backend/storage_async.rb +4 -0
- data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
- data/lib/3scale/backend/storage_async/client.rb +205 -0
- data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
- data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
- data/lib/3scale/backend/storage_helpers.rb +278 -0
- data/lib/3scale/backend/storage_key_helpers.rb +9 -0
- data/lib/3scale/backend/storage_sync.rb +43 -0
- data/lib/3scale/backend/transaction.rb +62 -0
- data/lib/3scale/backend/transactor.rb +177 -0
- data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
- data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
- data/lib/3scale/backend/transactor/notify_job.rb +47 -0
- data/lib/3scale/backend/transactor/process_job.rb +33 -0
- data/lib/3scale/backend/transactor/report_job.rb +84 -0
- data/lib/3scale/backend/transactor/status.rb +236 -0
- data/lib/3scale/backend/transactor/usage_report.rb +182 -0
- data/lib/3scale/backend/usage.rb +63 -0
- data/lib/3scale/backend/usage_limit.rb +115 -0
- data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
- data/lib/3scale/backend/util.rb +17 -0
- data/lib/3scale/backend/validators.rb +26 -0
- data/lib/3scale/backend/validators/base.rb +36 -0
- data/lib/3scale/backend/validators/key.rb +17 -0
- data/lib/3scale/backend/validators/limits.rb +57 -0
- data/lib/3scale/backend/validators/oauth_key.rb +15 -0
- data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
- data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
- data/lib/3scale/backend/validators/referrer.rb +60 -0
- data/lib/3scale/backend/validators/service_state.rb +15 -0
- data/lib/3scale/backend/validators/state.rb +15 -0
- data/lib/3scale/backend/version.rb +5 -0
- data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
- data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
- data/lib/3scale/backend/worker.rb +87 -0
- data/lib/3scale/backend/worker_async.rb +88 -0
- data/lib/3scale/backend/worker_metrics.rb +44 -0
- data/lib/3scale/backend/worker_sync.rb +32 -0
- data/lib/3scale/bundler_shim.rb +17 -0
- data/lib/3scale/prometheus_server.rb +10 -0
- data/lib/3scale/tasks/connectivity.rake +41 -0
- data/lib/3scale/tasks/helpers.rb +3 -0
- data/lib/3scale/tasks/helpers/environment.rb +23 -0
- data/lib/3scale/tasks/stats.rake +131 -0
- data/lib/3scale/tasks/swagger.rake +46 -0
- data/licenses.xml +1215 -0
- 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,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
|