active_record_in_time_scope 0.1.7

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b8fa7d1397cbad1a682309ad55bee699deabf406ea542ee48182e7880c87aa48
4
+ data.tar.gz: 8459cdc187fb8c7eb3d8a158e0d16ae2209a3664ba64d6a70c08c3e3908856b3
5
+ SHA512:
6
+ metadata.gz: ba9ca47a5b010da08753bb84ea270f82b8d1b0fb056d71962530370240c88b803b2739db51f844a75efa9d70b6b4bc938f3a0898116c006ea240b76d3fd34401
7
+ data.tar.gz: b9917fc20bc4d32e41d1e7466f56d4c766c86e10edde9d0a08f6563954120ebc63cf4d797448b27434b1812975e253aa03f44ba9bc9ce7fedab442770e64fd38
data/.rubocop.yml ADDED
@@ -0,0 +1,47 @@
1
+ AllCops:
2
+ TargetRubyVersion: 4.0
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Style/StringLiterals:
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Style/Lambda:
13
+ EnforcedStyle: literal
14
+
15
+ Metrics/AbcSize:
16
+ Enabled: false
17
+
18
+ Metrics/MethodLength:
19
+ Max: 40
20
+
21
+ Metrics/ModuleLength:
22
+ Enabled: false
23
+
24
+ Metrics/CyclomaticComplexity:
25
+ Enabled: false
26
+
27
+ Metrics/PerceivedComplexity:
28
+ Enabled: false
29
+
30
+ Metrics/BlockLength:
31
+ Max: 30
32
+ Exclude:
33
+ - "spec/**/*"
34
+ - "*.gemspec"
35
+
36
+ Layout/LineLength:
37
+ Max: 140
38
+
39
+ Style/Documentation:
40
+ Enabled: false
41
+
42
+ Style/OneClassPerFile:
43
+ Exclude:
44
+ - "spec/support/create_test_database.rb"
45
+
46
+ Gemspec/DevelopmentDependencies:
47
+ EnforcedStyle: gemspec
@@ -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
@@ -0,0 +1,87 @@
1
+ ---
2
+ targets:
3
+ - claudecode
4
+ root: true
5
+ ---
6
+
7
+ # Project Overview
8
+
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.
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ # Install dependencies
15
+ bin/setup
16
+
17
+ # Run all checks (linting + tests)
18
+ bundle exec rake
19
+
20
+ # Run tests only
21
+ bundle exec rspec
22
+
23
+ # Run a single test file
24
+ bundle exec rspec spec/in_time_scope_spec.rb
25
+
26
+ # Run a specific test by line number
27
+ bundle exec rspec spec/in_time_scope_spec.rb:10
28
+
29
+ # Run linting only
30
+ bundle exec rubocop
31
+
32
+ # Auto-fix linting issues
33
+ bundle exec rubocop -a
34
+
35
+ # Interactive console with gem loaded
36
+ bin/console
37
+
38
+ # Install gem locally
39
+ bundle exec rake install
40
+ ```
41
+
42
+ ## Code Style
43
+
44
+ - Ruby 3.0+ required
45
+ - Use double-quoted strings (enforced by RuboCop)
46
+ - All files must have `# frozen_string_literal: true` header
47
+
48
+ ## Architecture
49
+
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:
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
65
+
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).
67
+
68
+ Key configuration options for `in_time_scope`:
69
+ - First argument: scope name (default: `:in_time`)
70
+ - `start_at: { column: Symbol|nil, null: Boolean }` - start column config
71
+ - `end_at: { column: Symbol|nil, null: Boolean }` - end column config
72
+
73
+ Setting `column: nil` disables that boundary, enabling start-only (history) or end-only (expiration) patterns.
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
+
78
+ ## Test Structure
79
+
80
+ Tests use RSpec with SQLite3 in-memory database. Test models are defined in `spec/support/create_test_database.rb`:
81
+
82
+ - `Event` - basic nullable time window
83
+ - `Campaign` - non-nullable time window
84
+ - `Promotion` - custom column names
85
+ - `Article` - multiple scopes
86
+ - `History` - start-only pattern
87
+ - `Coupon` - end-only pattern
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-01-24
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 kyohah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # InTimeScope
2
+
3
+ [English](README.md) | [日本語](docs/ja/index.md) | [中文](docs/zh/index.md) | [Français](docs/fr/index.md) | [Deutsch](docs/de/index.md)
4
+
5
+ Are you writing this every time in Rails?
6
+
7
+ ```ruby
8
+ # Before
9
+ Event.where("start_at <= ? AND (end_at IS NULL OR end_at > ?)", Time.current, Time.current)
10
+
11
+ # After
12
+ class Event < ActiveRecord::Base
13
+ in_time_scope
14
+ end
15
+
16
+ Event.in_time
17
+ ```
18
+
19
+ That's it. One line of DSL, zero raw SQL in your models.
20
+
21
+ **This is a simple, thin gem that just provides scopes. No learning curve required.**
22
+
23
+ ## Why This Gem?
24
+
25
+ This gem exists to:
26
+
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
31
+
32
+ ## Recommended For
33
+
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
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ bundle add in_time_scope
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```ruby
47
+ class Event < ActiveRecord::Base
48
+ in_time_scope
49
+ end
50
+
51
+ # Class scope
52
+ Event.in_time # Records active now
53
+ Event.in_time(Time.parse("2024-06-01")) # Records active at specific time
54
+
55
+ # Instance method
56
+ event.in_time? # Is this record active now?
57
+ event.in_time?(some_time) # Was it active at that time?
58
+ ```
59
+
60
+ ## Features
61
+
62
+ ### Auto-Optimized SQL
63
+
64
+ The gem reads your schema and generates the right SQL:
65
+
66
+ ```ruby
67
+ # NULL-allowed columns → NULL-aware query
68
+ WHERE (start_at IS NULL OR start_at <= ?) AND (end_at IS NULL OR end_at > ?)
69
+
70
+ # NOT NULL columns → simple query
71
+ WHERE start_at <= ? AND end_at > ?
72
+ ```
73
+
74
+ ### Named Scopes
75
+
76
+ Multiple time windows per model:
77
+
78
+ ```ruby
79
+ class Article < ActiveRecord::Base
80
+ in_time_scope :published # → Article.in_time_published
81
+ in_time_scope :featured # → Article.in_time_featured
82
+ end
83
+ ```
84
+
85
+ ### Custom Columns
86
+
87
+ ```ruby
88
+ class Campaign < ActiveRecord::Base
89
+ in_time_scope start_at: { column: :available_at },
90
+ end_at: { column: :expired_at }
91
+ end
92
+ ```
93
+
94
+ ### Start-Only Pattern (Version History)
95
+
96
+ For records where each row is valid until the next one:
97
+
98
+ ```ruby
99
+ class Price < ActiveRecord::Base
100
+ in_time_scope start_at: { null: false }, end_at: { column: nil }
101
+ end
102
+
103
+ # Bonus: efficient has_one with NOT EXISTS
104
+ class User < ActiveRecord::Base
105
+ has_one :current_price, -> { latest_in_time(:user_id) }, class_name: "Price"
106
+ end
107
+
108
+ User.includes(:current_price) # No N+1, fetches only latest per user
109
+ ```
110
+
111
+ ### End-Only Pattern (Expiration)
112
+
113
+ For records that are active until they expire:
114
+
115
+ ```ruby
116
+ class Coupon < ActiveRecord::Base
117
+ in_time_scope start_at: { column: nil }, end_at: { null: false }
118
+ end
119
+ ```
120
+
121
+ ### Inverse Scopes
122
+
123
+ Query records outside the time window:
124
+
125
+ ```ruby
126
+ # Records not yet started (start_at > time)
127
+ Event.before_in_time
128
+ event.before_in_time?
129
+
130
+ # Records already ended (end_at <= time)
131
+ Event.after_in_time
132
+ event.after_in_time?
133
+
134
+ # Records outside time window (before OR after)
135
+ Event.out_of_time
136
+ event.out_of_time? # Logical inverse of in_time?
137
+ ```
138
+
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
145
+ ```
146
+
147
+ ## Options Reference
148
+
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 }` |
156
+
157
+ ## Acknowledgements
158
+
159
+ Inspired by [onk/shibaraku](https://github.com/onk/shibaraku). This gem extends the concept with:
160
+
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`
166
+
167
+ ## Development
168
+
169
+ ```bash
170
+ # Install dependencies
171
+ bin/setup
172
+
173
+ # Run tests
174
+ bundle exec rspec
175
+
176
+ # Run linting
177
+ bundle exec rubocop
178
+
179
+ # Generate CLAUDE.md (for AI coding assistants)
180
+ npx rulesync generate
181
+ ```
182
+
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`.
184
+
185
+ ## Contributing
186
+
187
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/kyohah/in_time_scope).
188
+
189
+ ## License
190
+
191
+ MIT License
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+ require "rspec/core/rake_task"
6
+
7
+ RuboCop::RakeTask.new
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task default: %i[rubocop spec]
data/Steepfile ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Steepfile for InTimeScope type checking
4
+
5
+ target :lib do
6
+ signature "sig"
7
+
8
+ check "lib"
9
+
10
+ # Use RBS collection for external gem types
11
+ collection_config "rbs_collection.yaml"
12
+
13
+ # Configure libraries
14
+ library "time"
15
+
16
+ # Ignore implementation details that use ActiveRecord internals
17
+ # The public API is properly typed, but internal methods use
18
+ # dynamic ActiveRecord features that are hard to type statically
19
+ configure_code_diagnostics do |hash|
20
+ # Allow untyped method calls for ActiveRecord dynamic methods
21
+ hash[Steep::Diagnostic::Ruby::NoMethod] = :hint
22
+ hash[Steep::Diagnostic::Ruby::UnknownInstanceVariable] = :hint
23
+ hash[Steep::Diagnostic::Ruby::RequiredBlockMissing] = :hint
24
+ end
25
+ end
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}"
@@ -0,0 +1,5 @@
1
+ # Inhaltsverzeichnis
2
+
3
+ - [Einführung](./index.md)
4
+ - [Punktesystem mit Ablaufdatum](./point-system.md)
5
+ - [Benutzernamen-Historie](./user-name-history.md)