redis-objects-periodical 0.7.0 → 0.8.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 +4 -4
- data/.agents/skills/dependabot-batch-maintenance/SKILL.md +141 -0
- data/.github/dependabot.yml +32 -3
- data/.github/scripts/dependabot-auto-merge.cjs +182 -0
- data/.github/scripts/dependabot-auto-merge.test.cjs +278 -0
- data/.github/scripts/dependabot-policy.cjs +132 -0
- data/.github/scripts/dependabot-policy.test.cjs +235 -0
- data/.github/scripts/timezone-inventory.cjs +48 -0
- data/.github/scripts/timezone-inventory.test.cjs +86 -0
- data/.github/workflows/checks.yml +124 -0
- data/.github/workflows/ci.yml +191 -0
- data/.github/workflows/dependabot-auto-merge.yml +61 -0
- data/.github/workflows/release.yml +59 -0
- data/.github/workflows/rubocop_challenge.yml +71 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +16 -16
- data/AGENTS.md +133 -0
- data/CHANGELOG.md +47 -0
- data/CONTRIBUTING.md +203 -0
- data/Dockerfile +3 -2
- data/Gemfile +6 -6
- data/Gemfile.lock +70 -65
- data/README.md +58 -221
- data/docker-compose.yml +1 -3
- data/docs/maintaining.md +237 -0
- data/docs/usage.md +216 -0
- data/docs/verification/2026-10-27-timezone.md +25 -0
- data/gemfiles/active_support/Gemfile +5 -0
- data/gemfiles/active_support/Gemfile.lock +137 -0
- data/lib/redis/objects/periodical/version.rb +1 -1
- data/lib/redis/objects/periodical_counters.rb +1 -1
- data/lib/redis/objects/periodical_hashes.rb +1 -1
- data/lib/redis/objects/periodical_sets.rb +1 -1
- data/lib/redis/objects/periodical_values.rb +1 -1
- data/lib/redis/periodical_counter.rb +1 -1
- data/lib/redis/periodical_hash_key.rb +1 -1
- data/lib/redis/periodical_set.rb +1 -1
- data/lib/redis/periodical_value.rb +1 -1
- data/lib/redis/recurring_at_intervals/weekly.rb +3 -1
- data/lib/redis/recurring_at_intervals.rb +3 -3
- data/redis-objects-periodical.gemspec +1 -1
- metadata +26 -8
- data/.circleci/config.yml +0 -212
data/README.md
CHANGED
|
@@ -1,263 +1,100 @@
|
|
|
1
|
-
[](https://github.com/ryz310/redis-objects-periodical/actions/workflows/ci.yml) [](https://badge.fury.io/rb/redis-objects-periodical)
|
|
2
|
+
[](https://qlty.sh/gh/ryz310/projects/redis-objects-periodical)
|
|
3
|
+
[](https://qlty.sh/gh/ryz310/projects/redis-objects-periodical)
|
|
2
4
|
|
|
3
5
|
# Redis::Objects::Periodical
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Extend [Redis::Objects](https://github.com/nateware/redis-objects) with counters,
|
|
8
|
+
values, sets, and hashes whose Redis keys are partitioned by period. Use them for
|
|
9
|
+
page views, active-user counts, rate-limiting data, and other periodical values.
|
|
6
10
|
|
|
7
11
|
## Installation
|
|
8
12
|
|
|
13
|
+
Ruby 3.3 or newer is required. Ruby 3.3, 3.4, and 4.0 are tested in CI.
|
|
14
|
+
The support policy follows stable Ruby branches receiving official normal or
|
|
15
|
+
security maintenance; preview releases are not supported. CI uses the latest
|
|
16
|
+
patch releases available in each supported series' CircleCI Ruby image.
|
|
17
|
+
See [Ruby's maintenance status](https://www.ruby-lang.org/en/downloads/branches/).
|
|
18
|
+
|
|
19
|
+
Ruby 2.7, 3.0, 3.1, and 3.2 are no longer supported. Applications using those
|
|
20
|
+
versions must upgrade Ruby before upgrading this gem, or remain on the last gem
|
|
21
|
+
release whose `required_ruby_version` permits their Ruby version.
|
|
22
|
+
|
|
9
23
|
Add this line to your application's Gemfile:
|
|
10
24
|
|
|
11
25
|
```ruby
|
|
12
26
|
gem 'redis-objects-periodical'
|
|
13
27
|
```
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
## Quick start
|
|
16
30
|
|
|
17
|
-
|
|
31
|
+
Configure the Redis connection as described in
|
|
32
|
+
[Redis::Objects](https://github.com/nateware/redis-objects), then declare a field:
|
|
18
33
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
model_name:id:field_name:yyyy-mm-dd
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
I recommend using with `expireat` option.
|
|
26
|
-
For illustration purposes, consider this stub class:
|
|
34
|
+
```ruby
|
|
35
|
+
require 'redis-objects-periodical'
|
|
27
36
|
|
|
28
|
-
```rb
|
|
29
37
|
class Homepage
|
|
30
38
|
include Redis::Objects
|
|
31
39
|
|
|
32
40
|
daily_counter :pv, expireat: -> { Time.now + 2_678_400 } # about a month
|
|
33
|
-
daily_hash_key :browsing_history, expireat: -> { Time.now + 2_678_400 } # about a month
|
|
34
|
-
daily_set :dau, expireat: -> { Time.now + 2_678_400 } # about a month
|
|
35
|
-
daily_value :cache, expireat: -> { Time.now + 2_678_400 } # about a month
|
|
36
41
|
|
|
37
42
|
def id
|
|
38
43
|
1
|
|
39
44
|
end
|
|
40
45
|
end
|
|
41
46
|
|
|
42
|
-
# 2021-04-01
|
|
43
47
|
homepage = Homepage.new
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
homepage.pv.increment
|
|
47
|
-
homepage.pv.increment
|
|
48
|
-
homepage.pv.increment
|
|
49
|
-
puts homepage.pv.value # 3
|
|
50
|
-
|
|
51
|
-
# 2021-04-02 (next day)
|
|
52
|
-
puts homepage.pv.value # 0
|
|
53
|
-
homepage.pv.increment
|
|
54
|
-
homepage.pv.increment
|
|
55
|
-
puts homepage.pv.value # 2
|
|
56
|
-
|
|
57
|
-
start_date = Date.new(2021, 4, 1)
|
|
58
|
-
end_date = Date.new(2021, 4, 2)
|
|
59
|
-
homepage.pv.range(start_date, end_date) # [3, 2]
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Periodical Counters
|
|
63
|
-
|
|
64
|
-
The periodical counters automatically switches the save destination when the date changes.
|
|
65
|
-
You can access past dates counted values like Ruby arrays:
|
|
66
|
-
|
|
67
|
-
```rb
|
|
68
|
-
# 2021-04-01
|
|
48
|
+
# On 2021-04-01:
|
|
69
49
|
homepage.pv.increment(3)
|
|
50
|
+
homepage.pv.value # => 3
|
|
70
51
|
|
|
71
|
-
# 2021-04-02
|
|
52
|
+
# On 2021-04-02, calling the field accessor selects the new day's key:
|
|
53
|
+
homepage.pv.value # => 0
|
|
72
54
|
homepage.pv.increment(2)
|
|
73
|
-
|
|
74
|
-
# 2021-04-03 (next day)
|
|
75
|
-
homepage.pv.increment(5)
|
|
76
|
-
|
|
77
|
-
homepage.pv[Date.new(2021, 4, 1)] # => 3
|
|
78
|
-
homepage.pv[Date.new(2021, 4, 1), 3] # => [3, 2, 5]
|
|
79
|
-
homepage.pv[Date.new(2021, 4, 1)..Date.new(2021, 4, 2)] # => [3, 2]
|
|
80
|
-
|
|
81
|
-
homepage.pv.delete_at(Date.new(2021, 4, 1))
|
|
82
|
-
homepage.pv.range(Date.new(2021, 4, 1), Date.new(2021, 4, 3)) # => [0, 2, 5]
|
|
83
|
-
homepage.pv.at(Date.new(2021, 4, 2)) # => #<Redis::Counter key="homepage:1:pv:2021-04-02">
|
|
84
|
-
homepage.pv.at(Date.new(2021, 4, 2)).value # 2
|
|
55
|
+
homepage.pv.range(Date.new(2021, 4, 1), Date.new(2021, 4, 2)) # => [3, 2]
|
|
85
56
|
```
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- Redis is a highly volatile key-value store, so I don't recommend using it.
|
|
92
|
-
- `monthly_counter`
|
|
93
|
-
- Key format: `model_name:id:field_name:yyyy-mm`
|
|
94
|
-
- `weekly_counter`
|
|
95
|
-
- Key format: `model_name:id:field_name:yyyyWw`
|
|
96
|
-
- `daily_counter`
|
|
97
|
-
- Key format: `model_name:id:field_name:yyyy-mm-dd`
|
|
98
|
-
- `hourly_counter`
|
|
99
|
-
- Key format: `model_name:id:field_name:yyyy-mm-ddThh`
|
|
100
|
-
- `minutely_counter`
|
|
101
|
-
- Key format: `model_name:id:field_name:yyyy-mm-ddThh:mi`
|
|
102
|
-
|
|
103
|
-
### Periodical Values
|
|
104
|
-
|
|
105
|
-
The periodical values automatically switches the save destination when the date changes.
|
|
106
|
-
|
|
107
|
-
```rb
|
|
108
|
-
# 2021-04-01
|
|
109
|
-
homepage.cache.value = 'a'
|
|
110
|
-
|
|
111
|
-
# 2021-04-02 (next day)
|
|
112
|
-
homepage.cache.value = 'b'
|
|
113
|
-
|
|
114
|
-
# 2021-04-03 (next day)
|
|
115
|
-
homepage.cache.value = 'c'
|
|
116
|
-
|
|
117
|
-
homepage.cache[Date.new(2021, 4, 1)] # => 'a'
|
|
118
|
-
homepage.cache[Date.new(2021, 4, 1), 3] # => ['a', 'b', 'c']
|
|
119
|
-
homepage.cache[Date.new(2021, 4, 1)..Date.new(2021, 4, 2)] # => ['a', 'b']
|
|
120
|
-
|
|
121
|
-
homepage.cache.delete_at(Date.new(2021, 4, 1))
|
|
122
|
-
homepage.cache.range(Date.new(2021, 4, 1), Date.new(2021, 4, 3)) # => [nil, 'b', 'c']
|
|
123
|
-
homepage.cache.at(Date.new(2021, 4, 2)) # => #<Redis::Value key="homepage:1:cache:2021-04-02">
|
|
124
|
-
homepage.cache.at(Date.new(2021, 4, 2)).value # 'b'
|
|
125
|
-
```
|
|
58
|
+
Keys are unique to the model, its ID, field, and period. This daily counter uses
|
|
59
|
+
`homepage:1:pv:2021-04-01`. Each field accessor creates a new periodical object;
|
|
60
|
+
an object retained in a variable keeps its original key across period boundaries.
|
|
61
|
+
Use an appropriate expiration option to limit retained period keys.
|
|
126
62
|
|
|
127
|
-
|
|
63
|
+
## Available periods and types
|
|
128
64
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
- Redis is a highly volatile key-value store, so I don't recommend using it.
|
|
132
|
-
- `monthly_value`
|
|
133
|
-
- Key format: `model_name:id:field_name:yyyy-mm`
|
|
134
|
-
- `weekly_value`
|
|
135
|
-
- Key format: `model_name:id:field_name:yyyyWw`
|
|
136
|
-
- `daily_value`
|
|
137
|
-
- Key format: `model_name:id:field_name:yyyy-mm-dd`
|
|
138
|
-
- `hourly_value`
|
|
139
|
-
- Key format: `model_name:id:field_name:yyyy-mm-ddThh`
|
|
140
|
-
- `minutely_value`
|
|
141
|
-
- Key format: `model_name:id:field_name:yyyy-mm-ddThh:mi`
|
|
65
|
+
Combine one of `annual`, `monthly`, `weekly`, `daily`, `hourly`, or `minutely`
|
|
66
|
+
with one of these field types:
|
|
142
67
|
|
|
143
|
-
|
|
68
|
+
| Type | Daily declaration | Single-period result | Multi-period result |
|
|
69
|
+
| --- | --- | --- | --- |
|
|
70
|
+
| Counter | `daily_counter` | Integer, missing as `0` | Array of integers |
|
|
71
|
+
| Value | `daily_value` | Stored value, missing as `nil` | Array of values |
|
|
72
|
+
| Set | `daily_set` | Members, missing as `[]` | Union of members |
|
|
73
|
+
| Hash | `daily_hash_key` | Hash, missing as `{}` | Hash with combined field values |
|
|
144
74
|
|
|
145
|
-
|
|
75
|
+
See the [usage guide](docs/usage.md) for declarations, key suffixes, weekly
|
|
76
|
+
year-boundary behavior, date/time arguments, aggregation, expiration, and timezone
|
|
77
|
+
selection. Rails/ActiveSupport is optional.
|
|
146
78
|
|
|
147
|
-
|
|
148
|
-
# 2021-04-01
|
|
149
|
-
homepage.browsing_history.incr('item1')
|
|
150
|
-
homepage.browsing_history.incr('item2')
|
|
151
|
-
homepage.browsing_history.incr('item2')
|
|
152
|
-
puts homepage.browsing_history.all # { 'item1' => '1', 'item2' => '2' }
|
|
79
|
+
## Development and maintenance
|
|
153
80
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
# 2021-04-03 (next day)
|
|
161
|
-
homepage.browsing_history.incr('item2')
|
|
162
|
-
homepage.browsing_history.incr('item4')
|
|
163
|
-
puts homepage.browsing_history.all # { 'item2' => '1', 'item4' => '1' }
|
|
164
|
-
|
|
165
|
-
homepage.browsing_history[Date.new(2021, 4, 1)] # => { 'item1' => '1', 'item2' => '2' }
|
|
166
|
-
homepage.browsing_history[Date.new(2021, 4, 1), 3] # => { 'item1' => '4', 'item2' => '3', 'item3' => '5', 'item4' => '1' }
|
|
167
|
-
homepage.browsing_history[Date.new(2021, 4, 1)..Date.new(2021, 4, 2)] # => { 'item1' => '4', 'item2' => '2', 'item3' => '5' }
|
|
168
|
-
|
|
169
|
-
homepage.browsing_history.delete_at(Date.new(2021, 4, 1))
|
|
170
|
-
homepage.browsing_history.range(Date.new(2021, 4, 1), Date.new(2021, 4, 3)) # => { 'item1' => '3', 'item2' => '1', 'item3' => '5', 'item4' => '1' }
|
|
171
|
-
homepage.browsing_history.at(Date.new(2021, 4, 2)) # => #<Redis::HashKey key="homepage:1:browsing_history:2021-04-02">
|
|
172
|
-
homepage.browsing_history.at(Date.new(2021, 4, 2)).all # { 'item1' => '3', 'item3' => '5' }
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
#### Periodical Hashes Family
|
|
176
|
-
|
|
177
|
-
- `annual_hash_key`
|
|
178
|
-
- Key format: `model_name:id:field_name:yyyy`
|
|
179
|
-
- Redis is a highly volatile key-value store, so I don't recommend using it.
|
|
180
|
-
- `monthly_hash_key`
|
|
181
|
-
- Key format: `model_name:id:field_name:yyyy-mm`
|
|
182
|
-
- `weekly_hash_key`
|
|
183
|
-
- Key format: `model_name:id:field_name:yyyyWw`
|
|
184
|
-
- `daily_hash_key`
|
|
185
|
-
- Key format: `model_name:id:field_name:yyyy-mm-dd`
|
|
186
|
-
- `hourly_hash_key`
|
|
187
|
-
- Key format: `model_name:id:field_name:yyyy-mm-ddThh`
|
|
188
|
-
- `minutely_hash_key`
|
|
189
|
-
- Key format: `model_name:id:field_name:yyyy-mm-ddThh:mi`
|
|
190
|
-
|
|
191
|
-
### Periodical Sets
|
|
192
|
-
|
|
193
|
-
The periodical sets also automatically switches the save destination when the date changes.
|
|
194
|
-
|
|
195
|
-
```rb
|
|
196
|
-
# 2021-04-01
|
|
197
|
-
homepage.dau << 'user1'
|
|
198
|
-
homepage.dau << 'user2'
|
|
199
|
-
homepage.dau << 'user1' # dup ignored
|
|
200
|
-
puts homepage.dau.members # ['user1', 'user2']
|
|
201
|
-
puts homepage.dau.length # 2
|
|
202
|
-
puts homepage.dau.count # alias of #length
|
|
203
|
-
|
|
204
|
-
# 2021-04-02 (next day)
|
|
205
|
-
puts homepage.dau.members # []
|
|
206
|
-
|
|
207
|
-
homepage.dau.merge('user2', 'user3')
|
|
208
|
-
puts homepage.dau.members # ['user2', 'user3']
|
|
209
|
-
|
|
210
|
-
# 2021-04-03 (next day)
|
|
211
|
-
homepage.dau.merge('user4')
|
|
212
|
-
|
|
213
|
-
homepage.dau[Date.new(2021, 4, 1)] # => ['user1', 'user2']
|
|
214
|
-
homepage.dau[Date.new(2021, 4, 1), 3] # => ['user1', 'user2', 'user3', 'user4']
|
|
215
|
-
homepage.dau[Date.new(2021, 4, 1)..Date.new(2021, 4, 2)] # => ['user1', 'user2', 'user3']
|
|
216
|
-
|
|
217
|
-
homepage.dau.delete_at(Date.new(2021, 4, 1))
|
|
218
|
-
homepage.dau.range(Date.new(2021, 4, 1), Date.new(2021, 4, 3)) # => ['user2', 'user3', 'user4']
|
|
219
|
-
homepage.dau.at(Date.new(2021, 4, 2)) # => #<Redis::Set key="homepage:1:dau:2021-04-02">
|
|
220
|
-
homepage.dau.at(Date.new(2021, 4, 2)).members # ['user2', 'user3']
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
#### Periodical Sets Family
|
|
224
|
-
|
|
225
|
-
- `annual_set`
|
|
226
|
-
- Key format: `model_name:id:field_name:yyyy`
|
|
227
|
-
- Redis is a highly volatile key-value store, so I don't recommend using it.
|
|
228
|
-
- `monthly_set`
|
|
229
|
-
- Key format: `model_name:id:field_name:yyyy-mm`
|
|
230
|
-
- `weekly_set`
|
|
231
|
-
- Key format: `model_name:id:field_name:yyyyWw`
|
|
232
|
-
- `daily_set`
|
|
233
|
-
- Key format: `model_name:id:field_name:yyyy-mm-dd`
|
|
234
|
-
- `hourly_set`
|
|
235
|
-
- Key format: `model_name:id:field_name:yyyy-mm-ddThh`
|
|
236
|
-
- `minutely_set`
|
|
237
|
-
- Key format: `model_name:id:field_name:yyyy-mm-ddThh:mi`
|
|
238
|
-
|
|
239
|
-
### Timezone
|
|
240
|
-
|
|
241
|
-
This gem follows Ruby process' time zone, but if you extends Time class by ActiveSupport (e.g. `Time.current`), follows Rails process' timezone.
|
|
242
|
-
|
|
243
|
-
## Development
|
|
244
|
-
|
|
245
|
-
The development environment for this gem is configured with docker-compose.
|
|
246
|
-
Please use the following command:
|
|
247
|
-
|
|
248
|
-
$ docker-compose up -d
|
|
249
|
-
$ docker-compose run --rm ruby bundle
|
|
250
|
-
$ docker-compose run --rm ruby rspec .
|
|
251
|
-
$ docker-compose run --rm ruby rubocop -a
|
|
81
|
+
- [Contributing](CONTRIBUTING.md): Docker setup, isolated Compose projects,
|
|
82
|
+
specs, lint, builds, CI, and optional ActiveSupport verification.
|
|
83
|
+
- [Maintaining](docs/maintaining.md): dependency updates, auto-merge, coverage
|
|
84
|
+
configuration, releases, scheduled maintenance, and CircleCI cutover/rollback.
|
|
85
|
+
- [Agent guidance](AGENTS.md): repository workflow and compatibility constraints.
|
|
86
|
+
- [Changelog](CHANGELOG.md): released changes.
|
|
252
87
|
|
|
253
88
|
## Contributing
|
|
254
89
|
|
|
255
|
-
Bug reports and pull requests are welcome on
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
90
|
+
Bug reports and pull requests are welcome on
|
|
91
|
+
[GitHub](https://github.com/ryz310/redis-objects-periodical).
|
|
92
|
+
Track unresolved bugs, improvements, and decisions in
|
|
93
|
+
[GitHub Issues](https://github.com/ryz310/redis-objects-periodical/issues).
|
|
94
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow.
|
|
260
95
|
|
|
261
|
-
##
|
|
96
|
+
## License and code of conduct
|
|
262
97
|
|
|
263
|
-
|
|
98
|
+
The gem is available under the [MIT License](LICENSE.txt).
|
|
99
|
+
Everyone interacting with the Redis::Objects::Periodical project is expected to
|
|
100
|
+
follow the [code of conduct](CODE_OF_CONDUCT.md).
|
data/docker-compose.yml
CHANGED
data/docs/maintaining.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Maintaining
|
|
2
|
+
|
|
3
|
+
See [Contributing](../CONTRIBUTING.md) for development and test commands.
|
|
4
|
+
Keep unresolved work in [GitHub Issues](https://github.com/ryz310/redis-objects-periodical/issues).
|
|
5
|
+
This guide describes procedures; it does not certify that external settings or
|
|
6
|
+
CircleCI cutover have been completed.
|
|
7
|
+
|
|
8
|
+
## Coverage reporting
|
|
9
|
+
|
|
10
|
+
To enable coverage reporting to Qlty, enable this repository in Qlty Cloud and
|
|
11
|
+
register a dedicated project coverage token as the GitHub Actions repository
|
|
12
|
+
secret `QLTY_COVERAGE_TOKEN`. Keep secret variables unavailable to fork and
|
|
13
|
+
Dependabot pull requests. When the token is absent, CI still merges and stores
|
|
14
|
+
coverage but explicitly skips the upload. When present, the SHA-pinned Qlty
|
|
15
|
+
Action publishes the merged report with `skip-errors: false`; upload errors fail
|
|
16
|
+
that job. Never commit the token or print its value in logs.
|
|
17
|
+
|
|
18
|
+
Qlty maintainability analysis and quality gates are configured separately in
|
|
19
|
+
Qlty Cloud. Review their results before making new checks required. Coverage
|
|
20
|
+
upload alone does not enable or verify those checks.
|
|
21
|
+
|
|
22
|
+
## Dependency updates and auto-merge
|
|
23
|
+
|
|
24
|
+
Dependabot checks Bundler and GitHub Actions weekly on Monday at 08:00 JST.
|
|
25
|
+
Bundler version updates wait 3 days for patches, 7 for minor updates, and 14 for
|
|
26
|
+
major updates (7 days by default). Actions updates use a 7-day default cooldown;
|
|
27
|
+
SemVer-specific cooldown settings are not applied to that ecosystem. These
|
|
28
|
+
cooldowns apply to version updates, not security updates.
|
|
29
|
+
|
|
30
|
+
Bundler updates cover both `/` and `/gemfiles/active_support`. The optional
|
|
31
|
+
integration uses a standard `Gemfile`/`Gemfile.lock` pair so Dependabot can discover
|
|
32
|
+
it. Its Gemfile evaluates the root Gemfile; review shared dependency updates in
|
|
33
|
+
both lockfiles. The `shared-dependencies` rule uses `group-by: dependency-name` to
|
|
34
|
+
combine each dependency's version update across the two directories into one PR,
|
|
35
|
+
while keeping different dependencies in separate PRs. See the
|
|
36
|
+
[Dependabot grouping reference](https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#group-by-groups).
|
|
37
|
+
Cross-directory grouping applies to version updates; review security updates for
|
|
38
|
+
the same lockfile consistency separately. Keep frozen installs enabled in CI.
|
|
39
|
+
|
|
40
|
+
Updates that touch the optional lockfile require manual review under the existing
|
|
41
|
+
auto-merge policy, including shared development dependency updates. ActiveSupport
|
|
42
|
+
remains optional, and changes to its selected series follow the
|
|
43
|
+
[version selection policy](../CONTRIBUTING.md#activesupport-version-selection).
|
|
44
|
+
For a manual shared dependency update, update the root constraint/lockfile and
|
|
45
|
+
resolve the optional lockfile with that dependency as the target, for example:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
docker compose run --rm -e BUNDLE_GEMFILE=gemfiles/active_support/Gemfile ruby bundle lock --update simplecov --conservative
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
When changing the monitored Bundler directories, let the configuration land on
|
|
52
|
+
`master` before using `@dependabot recreate` on PRs generated under the old
|
|
53
|
+
configuration. Verify that each shared dependency update includes both lockfiles
|
|
54
|
+
and passes the Ruby matrix, timezone inventory, and coverage collation. Recreation
|
|
55
|
+
overwrites manual changes; if Dependabot replaces the old PR with a grouped PR,
|
|
56
|
+
review the replacement. A successful configuration PR alone does not establish
|
|
57
|
+
that the hosted Dependabot update has synchronized both lockfiles.
|
|
58
|
+
|
|
59
|
+
`dependabot-auto-merge.yml` can reserve auto-merge for a narrow set of updates:
|
|
60
|
+
|
|
61
|
+
- Bundler patch/minor updates of `pry-byebug`, `rake`, `rspec`,
|
|
62
|
+
`rspec_junit_formatter`, `rubocop`, `rubocop-performance`, `rubocop-rspec`,
|
|
63
|
+
`simplecov`, `timecop`, and `yard`, changing only `Gemfile.lock`. The locked
|
|
64
|
+
runtime dependency entries (`redis-objects-periodical`, `redis-objects`,
|
|
65
|
+
`redis`, `redis-client`, and `connection_pool`) must stay identical. Only gem
|
|
66
|
+
spec/version and transitive dependency requirement lines may change.
|
|
67
|
+
- SHA-pinned patch/minor replacements for `actions/checkout`,
|
|
68
|
+
`actions/upload-artifact`, `actions/download-artifact`, and
|
|
69
|
+
`actions/github-script`, changing only `uses:` lines and their version comments
|
|
70
|
+
in existing workflow files.
|
|
71
|
+
|
|
72
|
+
Every named dependency in a grouped update must be permitted. Major updates,
|
|
73
|
+
Bundler itself, runtime dependencies, optional integration lockfiles, manifest
|
|
74
|
+
constraints, source/platform changes, third-party Actions, and workflow
|
|
75
|
+
permission/trigger/step changes require manual review. Unknown metadata, missing
|
|
76
|
+
or incomplete patches, a changed PR head, forks, and API errors do not enable
|
|
77
|
+
merging. The enable mutation also requires the exact inspected head commit.
|
|
78
|
+
If GitHub rejects enabling auto-merge specifically because the PR is already in
|
|
79
|
+
clean status, the workflow repeats policy and protection verification and checks
|
|
80
|
+
the current head, base, open/clean state, and absence of a reservation before
|
|
81
|
+
merging with an exact-head guard. Other enable errors fail the workflow.
|
|
82
|
+
|
|
83
|
+
The privileged workflow uses `pull_request_target` only to obtain the write
|
|
84
|
+
permissions unavailable on normal Dependabot PR events. It checks out only the
|
|
85
|
+
trusted base commit's `.github/scripts` policy with credentials disabled, never
|
|
86
|
+
executes PR code, and inspects changes through the GitHub API. Its trigger has a
|
|
87
|
+
scoped zizmor exception for this metadata-only design. Policy regressions run in
|
|
88
|
+
ordinary read-only CI:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
node --test .github/scripts/*.test.cjs
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Repository administrators must finish these settings after the new checks have
|
|
95
|
+
appeared on a PR:
|
|
96
|
+
|
|
97
|
+
1. Enable repository auto-merge with merge commits and preserve existing required
|
|
98
|
+
reviews, security checks, and other protections.
|
|
99
|
+
2. On `master`, require status checks and require the branch to be up to date.
|
|
100
|
+
Keep all three `checks / build_on_ruby_*` checks, `checks / rubocop`, `yardoc`,
|
|
101
|
+
and `upload-coverage`; add `active-support-timezone` and `dependabot-policy`.
|
|
102
|
+
3. Verify on an eligible Dependabot PR that a failing required check prevents
|
|
103
|
+
merging and that an excluded update stays manual. For existing PRs, let their
|
|
104
|
+
next update trigger the workflow.
|
|
105
|
+
|
|
106
|
+
Matching Dependabot PR events run the workflow without a separate enable
|
|
107
|
+
variable. The policy below verifies the repository settings before allowing a
|
|
108
|
+
merge.
|
|
109
|
+
|
|
110
|
+
The policy verifies the repository's auto-merge setting and the effective
|
|
111
|
+
rulesets plus any classic branch protection, including all eight named checks and
|
|
112
|
+
strict up-to-date checks, before reserving a merge. Preserve GitGuardian and all
|
|
113
|
+
other existing required checks. If protection cannot be read or is missing, it
|
|
114
|
+
rejects the update. On a current-head rejection or verification error (including
|
|
115
|
+
metadata-fetch failure), the workflow revokes an earlier reservation only when
|
|
116
|
+
its ownership can be verified. The reservation's merge commit body contains a
|
|
117
|
+
small policy marker identifying the repository, PR, inspected head, and workflow
|
|
118
|
+
run. Ownership requires both the GitHub Actions bot and a matching originating
|
|
119
|
+
workflow run; this marker remains in the eventual merge commit body. Human,
|
|
120
|
+
unrelated automation, and legacy unmarked reservations are preserved with a
|
|
121
|
+
manual-inspection notice. Failure to read ownership or confirm cancellation
|
|
122
|
+
fails the workflow and requires manual inspection of the PR's auto-merge state.
|
|
123
|
+
|
|
124
|
+
Runs for one PR are serialized, stale events do not change the current head's
|
|
125
|
+
reservation, and cancellation rechecks the head and reservation immediately
|
|
126
|
+
before writing and verifies the result afterward. GitHub's disable mutation has
|
|
127
|
+
no conditional head/reservation argument, so administrators and other automation
|
|
128
|
+
must avoid changing reservations concurrently with this workflow. A concurrent
|
|
129
|
+
external write between the final read and cancellation cannot be guarded
|
|
130
|
+
atomically by this API.
|
|
131
|
+
|
|
132
|
+
Merge queues are not supported. No branch-protection, repository-variable, or
|
|
133
|
+
publication setting is changed by the workflow. When rolling back,
|
|
134
|
+
[disable the Dependabot Auto Merge workflow](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/disable-and-enable-workflows)
|
|
135
|
+
to prevent new runs, cancel any active runs, and disable auto-merge on
|
|
136
|
+
already-reserved PRs separately. Disabling the workflow does not cancel active
|
|
137
|
+
runs or revoke existing reservations. Also inspect reservations manually if the
|
|
138
|
+
job is skipped (for example after retargeting away from `master`), checkout fails,
|
|
139
|
+
a run is cancelled or times out, or an
|
|
140
|
+
ownership marker's originating run is no longer available. Only the configured
|
|
141
|
+
PR events trigger re-evaluation; settings changes do not trigger it.
|
|
142
|
+
|
|
143
|
+
## Release and scheduled maintenance
|
|
144
|
+
|
|
145
|
+
`release.yml` uses RubyGems Trusted Publishing. Only pushes or manual dispatches
|
|
146
|
+
on `release` can publish; no repository enable variable is required. The workflow
|
|
147
|
+
reruns all three Ruby checks and RuboCop against the triggering commit, then
|
|
148
|
+
publishes that same commit using Ruby 3.3 and Bundler 4.0.23. YARD and Qlty do not
|
|
149
|
+
gate publishing. The publishing job uses fresh dependencies without caches,
|
|
150
|
+
checks for an existing version tag or published version, and is not cancelled by
|
|
151
|
+
a newer run. Do not run a real release as a migration test.
|
|
152
|
+
|
|
153
|
+
Before the first publish, register this gem's Trusted Publisher on RubyGems.org
|
|
154
|
+
with owner `ryz310`, repository `redis-objects-periodical`, workflow filename
|
|
155
|
+
`release.yml`, and environment `release`. Create the matching GitHub environment,
|
|
156
|
+
restrict it to the `release` branch, and configure its protection rules. The
|
|
157
|
+
publishing job alone receives `contents: write` and `id-token: write`; it does not
|
|
158
|
+
use `RUBYGEMS_API_KEY`. See the [RubyGems Trusted Publishing guide](https://guides.rubygems.org/trusted-publishing/).
|
|
159
|
+
|
|
160
|
+
`rubocop_challenge.yml` runs daily at 08:30 JST on `master` without a repository
|
|
161
|
+
enable variable. Manual dispatch on `master` defaults to a read-only dry run using
|
|
162
|
+
`--no-create-pr`. The CLI requires a token even for a dry run, so that job uses
|
|
163
|
+
the automatic read-only `GITHUB_TOKEN`.
|
|
164
|
+
Actual PR creation uses `GITHUB_TOKEN` with job-scoped `contents: write` and
|
|
165
|
+
`pull-requests: write`. Enable **Allow GitHub Actions to create and approve pull
|
|
166
|
+
requests** in the repository's Actions settings before PR creation. Check the
|
|
167
|
+
created PR's CI and approve pending workflow runs if required; PR creation alone
|
|
168
|
+
does not prove CI ran. Scheduled workflows run on the default branch, may be
|
|
169
|
+
delayed, and can be disabled after 60 days without public repository activity.
|
|
170
|
+
|
|
171
|
+
### Resume a release after removing the enable variables
|
|
172
|
+
|
|
173
|
+
1. Stop and drain the old CircleCI publisher, and confirm the Trusted Publisher
|
|
174
|
+
and GitHub `release` environment described above are configured. Inspect the
|
|
175
|
+
intended version's Git tag and RubyGems version before continuing. If either
|
|
176
|
+
already exists, investigate the partial release instead of retrying blindly.
|
|
177
|
+
2. Merge the workflow fix into `master` after its PR checks pass. This restores
|
|
178
|
+
the Challenge schedule on `master`; it does not update the release workflow
|
|
179
|
+
on the `release` branch.
|
|
180
|
+
3. Open and review a PR from `master` to `release`. Confirm its complete diff
|
|
181
|
+
contains only the changes intended for this release. Merging this PR pushes
|
|
182
|
+
to `release` and starts publication automatically, so merge only when ready
|
|
183
|
+
to publish. No enable variable or additional manual dispatch is needed.
|
|
184
|
+
4. Follow the new Release run. It must pass the three Ruby builds and RuboCop
|
|
185
|
+
before `publish` starts. Approve the `release` environment deployment if its
|
|
186
|
+
configured protection rules require approval. Confirm the version tag and
|
|
187
|
+
published RubyGems version after the job succeeds.
|
|
188
|
+
|
|
189
|
+
If the fixed workflow is already on `release` and no publish is in progress,
|
|
190
|
+
manual dispatch of **Release** with branch **release** starts a new release run.
|
|
191
|
+
Do not re-run the old skipped run: [a re-run uses its original commit](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs)
|
|
192
|
+
and still contains the removed conditions. Dispatching on `master` cannot publish.
|
|
193
|
+
|
|
194
|
+
## CircleCI migration checklist
|
|
195
|
+
|
|
196
|
+
The CircleCI configuration and badge have been removed. Repository administrators
|
|
197
|
+
must complete the external settings below; removing a file does not update branch
|
|
198
|
+
protection, stop already queued jobs, or disable workflows still present on the
|
|
199
|
+
old `release` branch. Stop each CircleCI writer before activating the matching
|
|
200
|
+
Actions workflow. The Challenge schedule becomes active when its workflow is on
|
|
201
|
+
`master`; publication starts on a push or manual dispatch on `release`.
|
|
202
|
+
|
|
203
|
+
1. Verify Actions on a normal PR, a fork PR, and a Dependabot PR. Check all three
|
|
204
|
+
Ruby builds, RuboCop, YARD, JUnit artifacts, and the three-input coverage merge.
|
|
205
|
+
Confirm missing-token skipping and authenticated Qlty reporting separately.
|
|
206
|
+
2. Once the Actions checks are available, use their exact reported
|
|
207
|
+
names when updating required checks. Replace only the `ci/circleci:` entries;
|
|
208
|
+
preserve `GitGuardian Security Checks`, strict up-to-date checks, and any
|
|
209
|
+
unrelated protections. Include all three Ruby builds, RuboCop, YARD, and
|
|
210
|
+
`upload-coverage`. Verify a failing test or missing coverage input blocks
|
|
211
|
+
merging before retiring CircleCI checks.
|
|
212
|
+
|
|
213
|
+
For the Ruby support update, also replace required checks for Ruby 2.7, 3.0,
|
|
214
|
+
3.1, and 3.2 with the reported checks for Ruby 3.3, 3.4, and 4.0. Keep RuboCop,
|
|
215
|
+
YARD, coverage, and unrelated protections required. Update protection only
|
|
216
|
+
after the new checks have run successfully.
|
|
217
|
+
3. Prepare the Trusted Publisher and protected `release` environment. Stop and
|
|
218
|
+
drain CircleCI publishing before pushing the release workflow to `release`.
|
|
219
|
+
Run the first real publish only as an explicitly requested release task;
|
|
220
|
+
first inspect existing tags and RubyGems versions, including any partial release.
|
|
221
|
+
4. Stop and drain the CircleCI Challenge schedule before merging the Actions
|
|
222
|
+
Challenge workflow into `master`. Run the Challenge dry run, then check an
|
|
223
|
+
actual generated PR and its CI before considering this step complete.
|
|
224
|
+
5. Stop the CircleCI project and confirm no queued or running jobs remain. Remove
|
|
225
|
+
obsolete CircleCI credentials and integration only after confirming they are
|
|
226
|
+
not shared with other projects. Keep the migration commit available for
|
|
227
|
+
restoring the previous configuration if needed.
|
|
228
|
+
|
|
229
|
+
For rollback, [disable the corresponding Actions workflow](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/disable-and-enable-workflows)
|
|
230
|
+
in the repository's Actions UI (or use `gh workflow disable release.yml` or
|
|
231
|
+
`gh workflow disable rubocop_challenge.yml`) and wait for any running writes to
|
|
232
|
+
finish before restoring the corresponding CircleCI writer. Inspect any partial
|
|
233
|
+
publication before proceeding. Restore previously recorded
|
|
234
|
+
required checks together with the previous CircleCI configuration and keep at
|
|
235
|
+
least one complete CI path active. Never enable both
|
|
236
|
+
publishers or both Challenge writers at once. Do not delete existing release tags
|
|
237
|
+
or retry publishing blindly after a partial failure.
|