in_time_scope 0.1.5 → 0.1.6
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/.rubocop.yml +1 -1
- data/.rulesync/commands/translate-readme.md +46 -0
- data/{CLAUDE.md → .rulesync/rules/project.md} +23 -7
- data/README.md +104 -221
- data/docs/book.toml +14 -0
- data/docs/de/SUMMARY.md +5 -0
- data/docs/de/index.md +192 -0
- data/docs/de/point-system.md +295 -0
- data/docs/de/user-name-history.md +164 -0
- data/docs/fr/SUMMARY.md +5 -0
- data/docs/fr/index.md +192 -0
- data/docs/fr/point-system.md +295 -0
- data/docs/fr/user-name-history.md +164 -0
- data/docs/ja/SUMMARY.md +5 -0
- data/docs/ja/index.md +192 -0
- data/docs/ja/point-system.md +295 -0
- data/docs/ja/user-name-history.md +164 -0
- data/docs/src/SUMMARY.md +5 -0
- data/docs/src/index.md +194 -0
- data/docs/src/point-system.md +295 -0
- data/docs/src/user-name-history.md +164 -0
- data/docs/zh/SUMMARY.md +5 -0
- data/docs/zh/index.md +192 -0
- data/docs/zh/point-system.md +295 -0
- data/docs/zh/user-name-history.md +164 -0
- data/lib/in_time_scope/class_methods.rb +139 -91
- data/lib/in_time_scope/version.rb +1 -1
- data/rulesync.jsonc +6 -0
- data/sig/in_time_scope.rbs +24 -14
- metadata +25 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f46c50d90f896b920a86f45ea49329a9629843c4c7550e065f22c4b20df35149
|
|
4
|
+
data.tar.gz: 6c927de51e73ba689314a7caaf5f419a23af0f61f35b80c09f1c5d4cfa63273d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d861b025f3f455367ef485e40df6f98d83e55b1fd3c3b5fdf2cc6f57bbae2dd3dec94586bb1265b76859a83a3bd0c782a523315cf5188e3ccf9f62a8dcb6b2f
|
|
7
|
+
data.tar.gz: f712f1b0e82dfa596c1d805ef5f7ddd715bc39eb1fffee564df6eb98a68582f21d1f9822f7980471d91c70823e86c1e693599adcb2d77eb96c909059e7d90576
|
data/.rubocop.yml
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
targets:
|
|
3
|
+
- claudecode
|
|
4
|
+
description: Translate README.md and sync docs for all languages (ja, zh, fr, de)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Translate Documentation
|
|
8
|
+
|
|
9
|
+
Translate and sync documentation for multiple languages.
|
|
10
|
+
|
|
11
|
+
## Instructions
|
|
12
|
+
|
|
13
|
+
### 1. Sync English Documentation
|
|
14
|
+
|
|
15
|
+
1. Read the current `README.md`
|
|
16
|
+
2. Copy content to `docs/src/index.md`:
|
|
17
|
+
- Remove the language links line (`[English](README.md) | [日本語]...`)
|
|
18
|
+
- Keep everything else
|
|
19
|
+
|
|
20
|
+
### 2. Translate to Other Languages
|
|
21
|
+
|
|
22
|
+
For each language directory (`docs/ja/`, `docs/zh/`, `docs/fr/`, `docs/de/`):
|
|
23
|
+
|
|
24
|
+
1. Translate `docs/src/index.md` to `docs/{lang}/index.md`
|
|
25
|
+
2. Translate `docs/src/point-system.md` to `docs/{lang}/point-system.md`
|
|
26
|
+
3. Translate `docs/src/user-name-history.md` to `docs/{lang}/user-name-history.md`
|
|
27
|
+
4. Update `docs/{lang}/SUMMARY.md` with translated titles
|
|
28
|
+
|
|
29
|
+
### 3. Translation Guidelines
|
|
30
|
+
|
|
31
|
+
For each translation:
|
|
32
|
+
- Keep all code blocks unchanged
|
|
33
|
+
- Translate all text content naturally (not literal translation)
|
|
34
|
+
- Keep the same markdown structure
|
|
35
|
+
- Keep URLs and links unchanged
|
|
36
|
+
- Do NOT include language links in docs files
|
|
37
|
+
- Translate headings and navigation text in SUMMARY.md
|
|
38
|
+
|
|
39
|
+
### 4. Language Codes
|
|
40
|
+
|
|
41
|
+
- `ja` - Japanese (日本語)
|
|
42
|
+
- `zh` - Chinese (中文)
|
|
43
|
+
- `fr` - French (Français)
|
|
44
|
+
- `de` - German (Deutsch)
|
|
45
|
+
|
|
46
|
+
$ARGUMENTS
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
targets:
|
|
3
|
+
- claudecode
|
|
4
|
+
root: true
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
7
|
+
# Project Overview
|
|
6
8
|
|
|
7
9
|
InTimeScope is a Ruby gem that adds time-window scopes to ActiveRecord models. It provides a convenient way to query records that fall within specific time periods (between `start_at` and `end_at` timestamps), with support for nullable columns, custom column names, and multiple scopes per model.
|
|
8
10
|
|
|
@@ -39,7 +41,7 @@ bundle exec rake install
|
|
|
39
41
|
|
|
40
42
|
## Code Style
|
|
41
43
|
|
|
42
|
-
- Ruby 3.
|
|
44
|
+
- Ruby 3.0+ required
|
|
43
45
|
- Use double-quoted strings (enforced by RuboCop)
|
|
44
46
|
- All files must have `# frozen_string_literal: true` header
|
|
45
47
|
|
|
@@ -47,8 +49,19 @@ bundle exec rake install
|
|
|
47
49
|
|
|
48
50
|
Entry point is `lib/in_time_scope.rb` which defines the `InTimeScope` module. When included in an ActiveRecord model, it provides the `in_time_scope` class method that generates:
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
+
**Primary scopes (records in time window):**
|
|
53
|
+
- `Model.in_time`, `Model.in_time(timestamp)` - class scope
|
|
54
|
+
- `instance.in_time?`, `instance.in_time?(timestamp)` - instance method
|
|
55
|
+
|
|
56
|
+
**Inverse scopes (records outside time window):**
|
|
57
|
+
- `Model.before_in_time` - records not yet started (`start_at > time`)
|
|
58
|
+
- `Model.after_in_time` - records already ended (`end_at <= time`)
|
|
59
|
+
- `Model.out_of_time` - records outside window (before OR after)
|
|
60
|
+
- Corresponding instance methods: `before_in_time?`, `after_in_time?`, `out_of_time?`
|
|
61
|
+
|
|
62
|
+
**Additional scopes for start-only/end-only patterns:**
|
|
63
|
+
- `Model.latest_in_time(:foreign_key)` - latest record per FK (for `has_one`)
|
|
64
|
+
- `Model.earliest_in_time(:foreign_key)` - earliest record per FK
|
|
52
65
|
|
|
53
66
|
The gem auto-detects column nullability from the database schema to generate optimized SQL queries (simpler queries for NOT NULL columns, NULL-aware queries otherwise).
|
|
54
67
|
|
|
@@ -59,6 +72,9 @@ Key configuration options for `in_time_scope`:
|
|
|
59
72
|
|
|
60
73
|
Setting `column: nil` disables that boundary, enabling start-only (history) or end-only (expiration) patterns.
|
|
61
74
|
|
|
75
|
+
Named scopes generate all methods with the scope name:
|
|
76
|
+
- `in_time_scope :published` → `in_time_published`, `before_in_time_published`, `after_in_time_published`, `out_of_time_published`
|
|
77
|
+
|
|
62
78
|
## Test Structure
|
|
63
79
|
|
|
64
80
|
Tests use RSpec with SQLite3 in-memory database. Test models are defined in `spec/support/create_test_database.rb`:
|
data/README.md
CHANGED
|
@@ -1,308 +1,191 @@
|
|
|
1
1
|
# InTimeScope
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](README.md) | [日本語](docs/ja/index.md) | [中文](docs/zh/index.md) | [Français](docs/fr/index.md) | [Deutsch](docs/de/index.md)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
This gem is inspired by [onk/shibaraku](https://github.com/onk/shibaraku). While shibaraku is a great gem, I wanted to extend it with additional features:
|
|
8
|
-
|
|
9
|
-
- **Nullable column handling**: shibaraku generates SQL with `OR` conditions when columns allow `NULL`, which can impact query performance. InTimeScope auto-detects column nullability from the schema and generates optimized queries.
|
|
10
|
-
- **Named scopes**: shibaraku only provides `in_time` method. InTimeScope allows multiple named scopes like `in_time_published`, `in_time_featured` per model.
|
|
11
|
-
- **Start-only / End-only patterns**: Support for versioned records (start_at only, no end_at) and expiration patterns (end_at only, no start_at).
|
|
12
|
-
- **has_one association support**: `latest_in_time` and `earliest_in_time` scopes optimized for `has_one` with `includes`, using NOT EXISTS subqueries.
|
|
13
|
-
|
|
14
|
-
These features required significant architectural changes, so I created a new gem rather than extending shibaraku.
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
Install the gem and add to the application's Gemfile by executing:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
bundle add in_time_scope
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
gem install in_time_scope
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Usage
|
|
31
|
-
|
|
32
|
-
### Basic: Nullable Time Window
|
|
33
|
-
Use the defaults (`start_at` / `end_at`) even when the columns allow `NULL`.
|
|
5
|
+
Are you writing this every time in Rails?
|
|
34
6
|
|
|
35
7
|
```ruby
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
t.datetime :end_at, null: true
|
|
39
|
-
|
|
40
|
-
t.timestamps
|
|
41
|
-
end
|
|
8
|
+
# Before
|
|
9
|
+
Event.where("start_at <= ? AND (end_at IS NULL OR end_at > ?)", Time.current, Time.current)
|
|
42
10
|
|
|
11
|
+
# After
|
|
43
12
|
class Event < ActiveRecord::Base
|
|
44
|
-
# Uses start_at / end_at by default
|
|
45
13
|
in_time_scope
|
|
46
14
|
end
|
|
47
15
|
|
|
48
16
|
Event.in_time
|
|
49
|
-
# => SELECT "events".* FROM "events" WHERE ("events"."start_at" IS NULL OR "events"."start_at" <= '2026-01-24 19:50:05.738232') AND ("events"."end_at" IS NULL OR "events"."end_at" > '2026-01-24 19:50:05.738232')
|
|
50
|
-
|
|
51
|
-
# Check at a specific time
|
|
52
|
-
Event.in_time(Time.parse("2024-06-01 12:00:00"))
|
|
53
|
-
|
|
54
|
-
# Is the current time within the window?
|
|
55
|
-
event = Event.first
|
|
56
|
-
event.in_time?
|
|
57
|
-
#=> true or false
|
|
58
|
-
|
|
59
|
-
# Check any arbitrary timestamp
|
|
60
|
-
event.in_time?(Time.parse("2024-06-01 12:00:00"))
|
|
61
|
-
#=> true or false
|
|
62
17
|
```
|
|
63
18
|
|
|
64
|
-
|
|
65
|
-
When both timestamps are required (no `NULL`s), the generated query is simpler and faster.
|
|
19
|
+
That's it. One line of DSL, zero raw SQL in your models.
|
|
66
20
|
|
|
67
|
-
|
|
68
|
-
create_table :events do |t|
|
|
69
|
-
t.datetime :start_at, null: false
|
|
70
|
-
t.datetime :end_at, null: false
|
|
21
|
+
**This is a simple, thin gem that just provides scopes. No learning curve required.**
|
|
71
22
|
|
|
72
|
-
|
|
73
|
-
end
|
|
23
|
+
## Why This Gem?
|
|
74
24
|
|
|
75
|
-
|
|
76
|
-
Event.in_time
|
|
77
|
-
# => SELECT "events".* FROM "events" WHERE ("events"."start_at" <= '2026-01-24 19:50:05.738232') AND ("events"."end_at" > '2026-01-24 19:50:05.738232')
|
|
25
|
+
This gem exists to:
|
|
78
26
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class Event < ActiveRecord::Base
|
|
84
|
-
# Explicitly mark columns as NOT NULL (even if the DB allows NULL)
|
|
85
|
-
in_time_scope start_at: { null: false }, end_at: { null: false }
|
|
86
|
-
end
|
|
87
|
-
```
|
|
27
|
+
- **Keep time-range logic consistent** across your entire codebase
|
|
28
|
+
- **Avoid copy-paste SQL** that's easy to get wrong
|
|
29
|
+
- **Make time a first-class domain concept** with named scopes like `in_time_published`
|
|
30
|
+
- **Auto-detect nullability** from your schema for optimized queries
|
|
88
31
|
|
|
89
|
-
|
|
90
|
-
Use these options in `in_time_scope` to customize column behavior.
|
|
32
|
+
## Recommended For
|
|
91
33
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
| `start_at: { column: ... }` | start_at | `Symbol` / `nil` | `:start_at` (or `:"<scope>_start_at"` when `:scope_name` is set) | Use a custom column name; set `nil` to disable `start_at` | `start_at: { column: :available_at }` |
|
|
96
|
-
| `end_at: { column: ... }` | end_at | `Symbol` / `nil` | `:end_at` (or `:"<scope>_end_at"` when `:scope_name` is set) | Use a custom column name; set `nil` to disable `end_at` | `end_at: { column: nil }` |
|
|
97
|
-
| `start_at: { null: ... }` | start_at | `true/false` | auto (schema) | Force NULL-aware vs NOT NULL behavior | `start_at: { null: false }` |
|
|
98
|
-
| `end_at: { null: ... }` | end_at | `true/false` | auto (schema) | Force NULL-aware vs NOT NULL behavior | `end_at: { null: true }` |
|
|
99
|
-
| `prefix: true` | scope_name | `true/false` | `false` | Use prefix style method name like `published_in_time` instead of `in_time_published` | `in_time_scope :published, prefix: true` |
|
|
34
|
+
- New Rails applications with validity periods
|
|
35
|
+
- Models with `start_at` / `end_at` columns
|
|
36
|
+
- Teams that want consistent time logic without scattered `where` clauses
|
|
100
37
|
|
|
101
|
-
|
|
102
|
-
Use this when periods never overlap and you want exactly one "current" row.
|
|
38
|
+
## Installation
|
|
103
39
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- the latest row is the current one
|
|
40
|
+
```bash
|
|
41
|
+
bundle add in_time_scope
|
|
42
|
+
```
|
|
108
43
|
|
|
109
|
-
|
|
44
|
+
## Quick Start
|
|
110
45
|
|
|
111
46
|
```ruby
|
|
112
47
|
class Event < ActiveRecord::Base
|
|
113
|
-
|
|
114
|
-
in_time_scope start_at: { null: false }, end_at: { column: nil }
|
|
48
|
+
in_time_scope
|
|
115
49
|
end
|
|
116
50
|
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
# Use .first with order to get the most recent single record
|
|
121
|
-
Event.in_time.order(start_at: :desc).first
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
With no `end_at`, each row implicitly ends at the next row's `start_at`.
|
|
125
|
-
The scope returns all matching records (WHERE only, no ORDER), so:
|
|
126
|
-
- Add `.order(start_at: :desc).first` for a single latest record
|
|
127
|
-
- Use `latest_in_time` for efficient `has_one` associations
|
|
128
|
-
|
|
129
|
-
Recommended index:
|
|
51
|
+
# Class scope
|
|
52
|
+
Event.in_time # Records active now
|
|
53
|
+
Event.in_time(Time.parse("2024-06-01")) # Records active at specific time
|
|
130
54
|
|
|
131
|
-
|
|
132
|
-
|
|
55
|
+
# Instance method
|
|
56
|
+
event.in_time? # Is this record active now?
|
|
57
|
+
event.in_time?(some_time) # Was it active at that time?
|
|
133
58
|
```
|
|
134
59
|
|
|
135
|
-
|
|
136
|
-
Use this when a record is active immediately and expires at `end_at`.
|
|
60
|
+
## Features
|
|
137
61
|
|
|
138
|
-
|
|
139
|
-
- `end_at` must be NOT NULL (a `ConfigurationError` is raised otherwise)
|
|
140
|
-
- `start_at` is not used (implicit "always active")
|
|
62
|
+
### Auto-Optimized SQL
|
|
141
63
|
|
|
142
|
-
|
|
64
|
+
The gem reads your schema and generates the right SQL:
|
|
143
65
|
|
|
144
66
|
```ruby
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
in_time_scope start_at: { column: nil }, end_at: { null: false }
|
|
148
|
-
end
|
|
67
|
+
# NULL-allowed columns → NULL-aware query
|
|
68
|
+
WHERE (start_at IS NULL OR start_at <= ?) AND (end_at IS NULL OR end_at > ?)
|
|
149
69
|
|
|
150
|
-
|
|
151
|
-
|
|
70
|
+
# NOT NULL columns → simple query
|
|
71
|
+
WHERE start_at <= ? AND end_at > ?
|
|
152
72
|
```
|
|
153
73
|
|
|
154
|
-
|
|
74
|
+
### Named Scopes
|
|
155
75
|
|
|
156
|
-
|
|
157
|
-
CREATE INDEX index_events_on_end_at ON events (end_at);
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Advanced: Custom Columns and Multiple Scopes
|
|
161
|
-
Customize which columns are used and define more than one time window per model.
|
|
76
|
+
Multiple time windows per model:
|
|
162
77
|
|
|
163
78
|
```ruby
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
t.datetime :published_start_at, null: false
|
|
168
|
-
t.datetime :published_end_at, null: false
|
|
169
|
-
|
|
170
|
-
t.timestamps
|
|
79
|
+
class Article < ActiveRecord::Base
|
|
80
|
+
in_time_scope :published # → Article.in_time_published
|
|
81
|
+
in_time_scope :featured # → Article.in_time_featured
|
|
171
82
|
end
|
|
172
|
-
|
|
173
|
-
class Event < ActiveRecord::Base
|
|
174
|
-
# Use different column names
|
|
175
|
-
in_time_scope start_at: { column: :available_at }, end_at: { column: :expired_at }
|
|
176
|
-
|
|
177
|
-
# Define an additional scope - uses published_start_at / published_end_at by default
|
|
178
|
-
in_time_scope :published
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
Event.in_time
|
|
182
|
-
# => uses available_at / expired_at
|
|
183
|
-
|
|
184
|
-
Event.in_time_published
|
|
185
|
-
# => uses published_start_at / published_end_at
|
|
186
83
|
```
|
|
187
84
|
|
|
188
|
-
###
|
|
189
|
-
Use the `prefix: true` option if you prefer the scope name as a prefix instead of suffix.
|
|
85
|
+
### Custom Columns
|
|
190
86
|
|
|
191
87
|
```ruby
|
|
192
|
-
class
|
|
193
|
-
|
|
194
|
-
|
|
88
|
+
class Campaign < ActiveRecord::Base
|
|
89
|
+
in_time_scope start_at: { column: :available_at },
|
|
90
|
+
end_at: { column: :expired_at }
|
|
195
91
|
end
|
|
196
|
-
|
|
197
|
-
Event.published_in_time
|
|
198
|
-
# => uses published_start_at / published_end_at
|
|
199
92
|
```
|
|
200
93
|
|
|
201
|
-
###
|
|
202
|
-
|
|
203
|
-
The start-only and end-only patterns provide `latest_in_time` and `earliest_in_time` scopes for efficient `has_one` associations.
|
|
204
|
-
|
|
205
|
-
**Note:** These scopes are NOT available for full time window patterns (both `start_at` and `end_at`), because the concept of "latest" or "earliest" is ambiguous when there's a time range.
|
|
94
|
+
### Start-Only Pattern (Version History)
|
|
206
95
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
`in_time` provides WHERE only. Add `order` externally:
|
|
96
|
+
For records where each row is valid until the next one:
|
|
210
97
|
|
|
211
98
|
```ruby
|
|
212
99
|
class Price < ActiveRecord::Base
|
|
213
|
-
belongs_to :user
|
|
214
|
-
|
|
215
100
|
in_time_scope start_at: { null: false }, end_at: { column: nil }
|
|
216
101
|
end
|
|
217
102
|
|
|
103
|
+
# Bonus: efficient has_one with NOT EXISTS
|
|
218
104
|
class User < ActiveRecord::Base
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
# in_time is WHERE only, add order externally
|
|
222
|
-
has_one :current_price,
|
|
223
|
-
-> { in_time.order(start_at: :desc) },
|
|
224
|
-
class_name: "Price"
|
|
105
|
+
has_one :current_price, -> { latest_in_time(:user_id) }, class_name: "Price"
|
|
225
106
|
end
|
|
107
|
+
|
|
108
|
+
User.includes(:current_price) # No N+1, fetches only latest per user
|
|
226
109
|
```
|
|
227
110
|
|
|
228
|
-
|
|
111
|
+
### End-Only Pattern (Expiration)
|
|
229
112
|
|
|
230
|
-
|
|
113
|
+
For records that are active until they expire:
|
|
231
114
|
|
|
232
115
|
```ruby
|
|
233
|
-
class
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# Uses NOT EXISTS subquery - only loads the latest record per user
|
|
237
|
-
has_one :current_price,
|
|
238
|
-
-> { latest_in_time(:user_id) },
|
|
239
|
-
class_name: "Price"
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
# Direct access
|
|
243
|
-
user.current_price
|
|
244
|
-
# => Returns the most recent price where start_at <= Time.current
|
|
245
|
-
|
|
246
|
-
# Efficient with includes (only fetches latest record per user from DB)
|
|
247
|
-
User.includes(:current_price).each do |user|
|
|
248
|
-
puts user.current_price&.amount
|
|
116
|
+
class Coupon < ActiveRecord::Base
|
|
117
|
+
in_time_scope start_at: { column: nil }, end_at: { null: false }
|
|
249
118
|
end
|
|
250
119
|
```
|
|
251
120
|
|
|
252
|
-
|
|
121
|
+
### Inverse Scopes
|
|
253
122
|
|
|
254
|
-
|
|
123
|
+
Query records outside the time window:
|
|
255
124
|
|
|
256
125
|
```ruby
|
|
257
|
-
|
|
258
|
-
|
|
126
|
+
# Records not yet started (start_at > time)
|
|
127
|
+
Event.before_in_time
|
|
128
|
+
event.before_in_time?
|
|
259
129
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
class_name: "Price"
|
|
264
|
-
end
|
|
130
|
+
# Records already ended (end_at <= time)
|
|
131
|
+
Event.after_in_time
|
|
132
|
+
event.after_in_time?
|
|
265
133
|
|
|
266
|
-
#
|
|
267
|
-
|
|
268
|
-
#
|
|
134
|
+
# Records outside time window (before OR after)
|
|
135
|
+
Event.out_of_time
|
|
136
|
+
event.out_of_time? # Logical inverse of in_time?
|
|
137
|
+
```
|
|
269
138
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
139
|
+
Works with named scopes too:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
Article.before_in_time_published # Not yet published
|
|
143
|
+
Article.after_in_time_published # Publication ended
|
|
144
|
+
Article.out_of_time_published # Not currently published
|
|
274
145
|
```
|
|
275
146
|
|
|
276
|
-
|
|
147
|
+
## Options Reference
|
|
277
148
|
|
|
278
|
-
|
|
149
|
+
| Option | Default | Description | Example |
|
|
150
|
+
| --- | --- | --- | --- |
|
|
151
|
+
| `scope_name` (1st arg) | `:in_time` | Named scope like `in_time_published` | `in_time_scope :published` |
|
|
152
|
+
| `start_at: { column: }` | `:start_at` | Custom column name, `nil` to disable | `start_at: { column: :available_at }` |
|
|
153
|
+
| `end_at: { column: }` | `:end_at` | Custom column name, `nil` to disable | `end_at: { column: nil }` |
|
|
154
|
+
| `start_at: { null: }` | auto-detect | Force NULL handling | `start_at: { null: false }` |
|
|
155
|
+
| `end_at: { null: }` | auto-detect | Force NULL handling | `end_at: { null: true }` |
|
|
279
156
|
|
|
280
|
-
|
|
157
|
+
## Acknowledgements
|
|
281
158
|
|
|
282
|
-
|
|
283
|
-
class Event < ActiveRecord::Base
|
|
284
|
-
# This will raise ColumnNotFoundError if hoge_start_at or hoge_end_at columns don't exist
|
|
285
|
-
in_time_scope :hoge
|
|
286
|
-
end
|
|
287
|
-
# => InTimeScope::ColumnNotFoundError: Column 'hoge_start_at' does not exist on table 'events'
|
|
288
|
-
```
|
|
159
|
+
Inspired by [onk/shibaraku](https://github.com/onk/shibaraku). This gem extends the concept with:
|
|
289
160
|
|
|
290
|
-
|
|
161
|
+
- Schema-aware NULL handling for optimized queries
|
|
162
|
+
- Multiple named scopes per model
|
|
163
|
+
- Start-only / End-only patterns
|
|
164
|
+
- `latest_in_time` / `earliest_in_time` for efficient `has_one` associations
|
|
165
|
+
- Inverse scopes: `before_in_time`, `after_in_time`, `out_of_time`
|
|
291
166
|
|
|
292
167
|
## Development
|
|
293
168
|
|
|
294
|
-
|
|
169
|
+
```bash
|
|
170
|
+
# Install dependencies
|
|
171
|
+
bin/setup
|
|
295
172
|
|
|
296
|
-
|
|
173
|
+
# Run tests
|
|
174
|
+
bundle exec rspec
|
|
297
175
|
|
|
298
|
-
|
|
176
|
+
# Run linting
|
|
177
|
+
bundle exec rubocop
|
|
299
178
|
|
|
300
|
-
|
|
179
|
+
# Generate CLAUDE.md (for AI coding assistants)
|
|
180
|
+
npx rulesync generate
|
|
181
|
+
```
|
|
301
182
|
|
|
302
|
-
|
|
183
|
+
This project uses [rulesync](https://github.com/dyoshikawa/rulesync) to manage AI assistant rules. Edit `.rulesync/rules/*.md` and run `npx rulesync generate` to update `CLAUDE.md`.
|
|
303
184
|
|
|
304
|
-
|
|
185
|
+
## Contributing
|
|
305
186
|
|
|
306
|
-
|
|
187
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/kyohah/in_time_scope).
|
|
188
|
+
|
|
189
|
+
## License
|
|
307
190
|
|
|
308
|
-
|
|
191
|
+
MIT License
|
data/docs/book.toml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[book]
|
|
2
|
+
title = "InTimeScope"
|
|
3
|
+
authors = ["kyohah"]
|
|
4
|
+
language = "en"
|
|
5
|
+
src = "src"
|
|
6
|
+
|
|
7
|
+
[build]
|
|
8
|
+
build-dir = "book"
|
|
9
|
+
|
|
10
|
+
[output.html]
|
|
11
|
+
default-theme = "light"
|
|
12
|
+
preferred-dark-theme = "navy"
|
|
13
|
+
git-repository-url = "https://github.com/kyohah/in_time_scope"
|
|
14
|
+
edit-url-template = "https://github.com/kyohah/in_time_scope/edit/main/docs/{path}"
|