active_record-events 3.0.0 → 4.1.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/README.md +44 -4
- data/Rakefile +18 -2
- data/lib/active_record/events/extension.rb +19 -0
- data/lib/active_record/events/method_factory.rb +104 -0
- data/lib/active_record/events/naming.rb +6 -1
- data/lib/active_record/events/version.rb +1 -1
- data/lib/active_record/events.rb +6 -50
- data/lib/generators/active_record/event/event_generator.rb +5 -2
- data/spec/active_record/events/method_factory_spec.rb +59 -0
- data/spec/active_record/events/naming_spec.rb +23 -12
- data/spec/active_record/events_spec.rb +60 -11
- data/spec/dummy/app/models/task.rb +1 -0
- data/spec/dummy/config/boot.rb +1 -0
- data/spec/dummy/config/database.yml +3 -3
- data/spec/dummy/db/migrate/20220609015338_add_expired_at_to_tasks.rb +5 -0
- data/spec/dummy/db/schema.rb +7 -6
- data/spec/generators/active_record/event/event_generator_spec.rb +8 -2
- data/spec/spec_helper.rb +16 -3
- data/spec/support/{factories.rb → factories/task_factory.rb} +0 -0
- data/spec/support/{generator_helpers.rb → helpers/generator_helpers.rb} +0 -0
- data/spec/support/matchers/have_method.rb +5 -0
- metadata +39 -22
- data/lib/active_record/events/verbs.rb +0 -5
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1eace6bdc4fb78e2ac6c90a8521c976f8b707caeed3e035da39d0218e0724ad7
|
4
|
+
data.tar.gz: 8e9754766994b37d29dd29a9a44963fa9c90b1d4f90418e8e06bf3863f32ea02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1260729b2209565867c7824850be4450b45565da6fc1d7864d247ff6fcc659b89f1969ef2f9530e2bfc7d8de934a90f3eab585c18fb2c0d8ec3809f8242a864f
|
7
|
+
data.tar.gz: b876227c57657edb00198bda46eb42e2b2aebdb39379cd61261c9586805a5e736575515b2ddbbd336ed7e1612b603b0f6c30982b3c8e721da9ef849a93f41a2f
|
data/README.md
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
-
# ActiveRecord::Events [](https://rubygems.org/gems/active_record-events) [](https://github.com/pienkowb/active_record-events/actions/workflows/test.yml?query=branch%3Adevelop) [](https://coveralls.io/github/pienkowb/active_record-events) [](https://codeclimate.com/github/pienkowb/active_record-events)
|
2
2
|
|
3
3
|
An ActiveRecord extension providing convenience methods for timestamp management.
|
4
4
|
|
5
|
+
## Screencast
|
6
|
+
|
7
|
+
<a href="https://www.youtube.com/watch?v=TIR7YDF3O-4">
|
8
|
+
<img src="https://img.youtube.com/vi/TIR7YDF3O-4/maxresdefault.jpg" title="ActiveRecord::Events - Awesome Ruby Gems" width="50%">
|
9
|
+
</a>
|
10
|
+
|
11
|
+
[Watch screencast](https://www.youtube.com/watch?v=TIR7YDF3O-4) (courtesy of [Mike Rogers](https://github.com/MikeRogers0))
|
12
|
+
|
5
13
|
## Installation
|
6
14
|
|
7
15
|
Add the following line to your application's Gemfile:
|
@@ -93,12 +101,42 @@ In such a case, many lines of code can be replaced with an expressive one-liner.
|
|
93
101
|
has_events :complete, :archive
|
94
102
|
```
|
95
103
|
|
96
|
-
###
|
104
|
+
### Date fields
|
105
|
+
|
106
|
+
In case of date fields, which by convention have names ending with `_on` instead of `_at` (e.g. `completed_on`), the `field_type` option needs to be passed to the macro:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
has_event :complete, field_type: :date
|
110
|
+
```
|
111
|
+
|
112
|
+
### Custom field name
|
113
|
+
|
114
|
+
If there's a field with a name that doesn't follow the naming convention (i.e. does not end with `_at` or `_on`), you can pass it as the `field_name` option.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
has_event :complete, field_name: :completion_time
|
118
|
+
```
|
119
|
+
|
120
|
+
Note that the `field_name` option takes precedence over the `field_type` option.
|
121
|
+
|
122
|
+
### Comparison strategy
|
123
|
+
|
124
|
+
By default the timestamp's presence will dictate the behavior. However in some cases you may want to check against the current time.
|
97
125
|
|
98
|
-
|
126
|
+
You can do this with the `strategy` option, which can be either `presence` or `time_comparison`:
|
99
127
|
|
100
128
|
```ruby
|
101
|
-
has_event :
|
129
|
+
has_event :complete, strategy: :time_comparison
|
130
|
+
```
|
131
|
+
|
132
|
+
**Example:**
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
task.completed_at = 1.hour.ago
|
136
|
+
task.completed? # => true
|
137
|
+
|
138
|
+
task.completed_at = 1.hour.from_now
|
139
|
+
task.completed? # => false
|
102
140
|
```
|
103
141
|
|
104
142
|
### Specifying an object
|
@@ -189,6 +227,8 @@ end
|
|
189
227
|
- [Bartosz Pieńkowski](https://github.com/pienkowb)
|
190
228
|
- [Tomasz Skupiński](https://github.com/tskupinski)
|
191
229
|
- [Oskar Janusz](https://github.com/oskaror)
|
230
|
+
- [Mike Rogers](https://github.com/MikeRogers0)
|
231
|
+
- [Spencer Rogers](https://github.com/serogers)
|
192
232
|
|
193
233
|
## See also
|
194
234
|
|
data/Rakefile
CHANGED
@@ -22,9 +22,23 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
22
22
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
23
|
end
|
24
24
|
|
25
|
-
require '
|
25
|
+
require 'active_record'
|
26
|
+
require 'yaml'
|
26
27
|
|
27
|
-
|
28
|
+
include ActiveRecord::Tasks
|
29
|
+
|
30
|
+
dummy_root = File.expand_path('spec/dummy', __dir__)
|
31
|
+
database_config = YAML.load_file("#{dummy_root}/config/database.yml")
|
32
|
+
|
33
|
+
DatabaseTasks.root = dummy_root
|
34
|
+
DatabaseTasks.env = ENV['RAILS_ENV'] || 'development'
|
35
|
+
DatabaseTasks.db_dir = "#{dummy_root}/db"
|
36
|
+
DatabaseTasks.database_configuration = database_config
|
37
|
+
DatabaseTasks.migrations_paths = "#{dummy_root}/db/migrate"
|
38
|
+
|
39
|
+
task :environment do
|
40
|
+
require "#{dummy_root}/config/environment.rb"
|
41
|
+
end
|
28
42
|
|
29
43
|
ACTIVE_RECORD_MIGRATION_CLASS =
|
30
44
|
if ActiveRecord::VERSION::MAJOR >= 5
|
@@ -33,6 +47,8 @@ ACTIVE_RECORD_MIGRATION_CLASS =
|
|
33
47
|
ActiveRecord::Migration
|
34
48
|
end
|
35
49
|
|
50
|
+
load 'active_record/railties/databases.rake'
|
51
|
+
|
36
52
|
Bundler::GemHelper.install_tasks
|
37
53
|
|
38
54
|
require 'rspec/core'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'active_record/events/method_factory'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Events
|
5
|
+
module Extension
|
6
|
+
def has_events(*names)
|
7
|
+
options = names.extract_options!
|
8
|
+
names.each { |n| has_event(n, options) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def has_event(name, options = {})
|
12
|
+
method_factory = MethodFactory.new(name, options)
|
13
|
+
|
14
|
+
include method_factory.instance_methods
|
15
|
+
extend method_factory.class_methods
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'active_record/events/naming'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Events
|
5
|
+
class MethodFactory
|
6
|
+
def initialize(event_name, options)
|
7
|
+
@options = options
|
8
|
+
@naming = Naming.new(event_name, options)
|
9
|
+
@strategy = options[:strategy].try(:to_sym)
|
10
|
+
end
|
11
|
+
|
12
|
+
def instance_methods
|
13
|
+
Module.new.tap do |module_|
|
14
|
+
define_predicate_method(module_, naming, strategy)
|
15
|
+
define_inverse_predicate_method(module_, naming)
|
16
|
+
|
17
|
+
define_action_method(module_, naming)
|
18
|
+
define_safe_action_method(module_, naming)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def class_methods
|
23
|
+
Module.new.tap do |module_|
|
24
|
+
define_collective_action_method(module_, naming)
|
25
|
+
|
26
|
+
unless options[:skip_scopes]
|
27
|
+
define_scope_method(module_, naming, strategy)
|
28
|
+
define_inverse_scope_method(module_, naming, strategy)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :options
|
36
|
+
attr_reader :naming
|
37
|
+
attr_reader :strategy
|
38
|
+
|
39
|
+
def define_predicate_method(module_, naming, strategy)
|
40
|
+
module_.send(:define_method, naming.predicate) do
|
41
|
+
if strategy == :time_comparison
|
42
|
+
self[naming.field].present? && self[naming.field].past?
|
43
|
+
else
|
44
|
+
self[naming.field].present?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def define_inverse_predicate_method(module_, naming)
|
50
|
+
module_.send(:define_method, naming.inverse_predicate) do
|
51
|
+
!__send__(naming.predicate)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def define_action_method(module_, naming)
|
56
|
+
module_.send(:define_method, naming.action) do
|
57
|
+
if persisted?
|
58
|
+
touch(naming.field)
|
59
|
+
else
|
60
|
+
self[naming.field] = current_time_from_proper_timezone
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def define_safe_action_method(module_, naming)
|
66
|
+
module_.send(:define_method, naming.safe_action) do
|
67
|
+
__send__(naming.action) if __send__(naming.inverse_predicate)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def define_collective_action_method(module_, naming)
|
72
|
+
module_.send(:define_method, naming.collective_action) do
|
73
|
+
if respond_to?(:touch_all)
|
74
|
+
touch_all(naming.field)
|
75
|
+
else
|
76
|
+
update_all(naming.field => Time.current)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def define_scope_method(module_, naming, strategy)
|
82
|
+
module_.send(:define_method, naming.scope) do
|
83
|
+
if strategy == :time_comparison
|
84
|
+
where(arel_table[naming.field].lteq(Time.current))
|
85
|
+
else
|
86
|
+
where(arel_table[naming.field].not_eq(nil))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def define_inverse_scope_method(module_, naming, strategy)
|
92
|
+
module_.send(:define_method, naming.inverse_scope) do
|
93
|
+
arel_field = arel_table[naming.field]
|
94
|
+
|
95
|
+
if strategy == :time_comparison
|
96
|
+
where(arel_field.eq(nil).or(arel_field.gt(Time.current)))
|
97
|
+
else
|
98
|
+
where(arel_field.eq(nil))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'verbs'
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Events
|
@@ -6,11 +6,15 @@ module ActiveRecord
|
|
6
6
|
def initialize(infinitive, options = {})
|
7
7
|
@infinitive = infinitive
|
8
8
|
@object = options[:object].presence
|
9
|
+
@field_name = options[:field_name].to_s
|
9
10
|
@field_type = options[:field_type].try(:to_sym)
|
10
11
|
end
|
11
12
|
|
12
13
|
def field
|
14
|
+
return field_name if field_name.present?
|
15
|
+
|
13
16
|
suffix = field_type == :date ? 'on' : 'at'
|
17
|
+
|
14
18
|
concatenate(object, past_participle, suffix)
|
15
19
|
end
|
16
20
|
|
@@ -46,6 +50,7 @@ module ActiveRecord
|
|
46
50
|
|
47
51
|
attr_reader :infinitive
|
48
52
|
attr_reader :object
|
53
|
+
attr_reader :field_name
|
49
54
|
attr_reader :field_type
|
50
55
|
|
51
56
|
def concatenate(*parts)
|
data/lib/active_record/events.rb
CHANGED
@@ -1,57 +1,13 @@
|
|
1
1
|
require 'active_support'
|
2
|
-
require 'active_record/events/naming'
|
3
|
-
|
4
|
-
module ActiveRecord
|
5
|
-
module Events
|
6
|
-
def has_events(*names)
|
7
|
-
options = names.extract_options!
|
8
|
-
names.each { |n| has_event(n, options) }
|
9
|
-
end
|
10
|
-
|
11
|
-
def has_event(name, options = {})
|
12
|
-
naming = Naming.new(name, options)
|
13
|
-
|
14
|
-
include(Module.new do
|
15
|
-
define_method(naming.predicate) do
|
16
|
-
self[naming.field].present?
|
17
|
-
end
|
18
|
-
|
19
|
-
define_method(naming.inverse_predicate) do
|
20
|
-
!__send__(naming.predicate)
|
21
|
-
end
|
22
2
|
|
23
|
-
|
24
|
-
touch(naming.field)
|
25
|
-
end
|
3
|
+
require 'active_record/events/version'
|
26
4
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end)
|
31
|
-
|
32
|
-
extend(Module.new do
|
33
|
-
define_method(naming.collective_action) do
|
34
|
-
if respond_to?(:touch_all)
|
35
|
-
touch_all(naming.field)
|
36
|
-
else
|
37
|
-
update_all(naming.field => Time.current)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
unless options[:skip_scopes]
|
42
|
-
define_method(naming.scope) do
|
43
|
-
where(arel_table[naming.field].not_eq(nil))
|
44
|
-
end
|
5
|
+
require 'active_record/events/naming'
|
6
|
+
require 'active_record/events/method_factory'
|
7
|
+
require 'active_record/events/extension'
|
45
8
|
|
46
|
-
|
47
|
-
where(arel_table[naming.field].eq(nil))
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
9
|
+
require 'active_record/events/macro'
|
54
10
|
|
55
11
|
ActiveSupport.on_load(:active_record) do
|
56
|
-
extend ActiveRecord::Events
|
12
|
+
extend ActiveRecord::Events::Extension
|
57
13
|
end
|
@@ -6,7 +6,7 @@ require 'active_record/events/macro'
|
|
6
6
|
module ActiveRecord
|
7
7
|
module Generators
|
8
8
|
class EventGenerator < Rails::Generators::Base
|
9
|
-
MACRO_OPTIONS = %w[object field_type skip_scopes].freeze
|
9
|
+
MACRO_OPTIONS = %w[object field_type skip_scopes strategy].freeze
|
10
10
|
|
11
11
|
argument :model_name, type: :string
|
12
12
|
argument :event_name, type: :string
|
@@ -17,6 +17,8 @@ module ActiveRecord
|
|
17
17
|
desc: 'The field type (datetime or date)'
|
18
18
|
class_option :object, type: :string,
|
19
19
|
desc: 'The name of the object'
|
20
|
+
class_option :strategy, type: :string,
|
21
|
+
desc: 'The comparison strategy (presence or time_comparison)'
|
20
22
|
|
21
23
|
source_root File.expand_path('templates', __dir__)
|
22
24
|
|
@@ -38,8 +40,9 @@ module ActiveRecord
|
|
38
40
|
|
39
41
|
macro_options = options.slice(*MACRO_OPTIONS)
|
40
42
|
macro = ActiveRecord::Events::Macro.new(event_name, macro_options)
|
43
|
+
pattern = /^\s*class\s.+\n/
|
41
44
|
|
42
|
-
inject_into_file model_file_path, "\s\s#{macro}\n", after:
|
45
|
+
inject_into_file model_file_path, "\s\s#{macro}\n", after: pattern
|
43
46
|
end
|
44
47
|
|
45
48
|
private
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe ActiveRecord::Events::MethodFactory do
|
4
|
+
let(:event_name) { :complete }
|
5
|
+
let(:options) { {} }
|
6
|
+
|
7
|
+
subject { described_class.new(event_name, options) }
|
8
|
+
|
9
|
+
it 'generates instance methods' do
|
10
|
+
result = subject.instance_methods
|
11
|
+
|
12
|
+
expect(result).to have_method(:completed?)
|
13
|
+
expect(result).to have_method(:not_completed?)
|
14
|
+
expect(result).to have_method(:complete!)
|
15
|
+
expect(result).to have_method(:complete)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'generates class methods' do
|
19
|
+
result = subject.class_methods
|
20
|
+
|
21
|
+
expect(result).to have_method(:complete_all)
|
22
|
+
expect(result).to have_method(:completed)
|
23
|
+
expect(result).to have_method(:not_completed)
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with the object option' do
|
27
|
+
let(:options) { { object: :task } }
|
28
|
+
|
29
|
+
it 'generates instance methods' do
|
30
|
+
result = subject.instance_methods
|
31
|
+
|
32
|
+
expect(result).to have_method(:task_completed?)
|
33
|
+
expect(result).to have_method(:task_not_completed?)
|
34
|
+
expect(result).to have_method(:complete_task!)
|
35
|
+
expect(result).to have_method(:complete_task)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'generates class methods' do
|
39
|
+
result = subject.class_methods
|
40
|
+
|
41
|
+
expect(result).to have_method(:complete_all_tasks)
|
42
|
+
expect(result).to have_method(:task_completed)
|
43
|
+
expect(result).to have_method(:task_not_completed)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with the skip scopes option' do
|
48
|
+
let(:options) { { skip_scopes: true } }
|
49
|
+
|
50
|
+
it 'generates class methods without scopes' do
|
51
|
+
result = subject.class_methods
|
52
|
+
|
53
|
+
expect(result).to have_method(:complete_all)
|
54
|
+
|
55
|
+
expect(result).not_to have_method(:completed)
|
56
|
+
expect(result).not_to have_method(:not_completed)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe ActiveRecord::Events::Naming do
|
4
|
-
|
4
|
+
let(:event_name) { :complete }
|
5
|
+
let(:options) { {} }
|
6
|
+
|
7
|
+
subject { described_class.new(event_name, options) }
|
5
8
|
|
6
9
|
it 'generates a field name' do
|
7
10
|
expect(subject.field).to eq('completed_at')
|
@@ -36,46 +39,54 @@ RSpec.describe ActiveRecord::Events::Naming do
|
|
36
39
|
end
|
37
40
|
|
38
41
|
context 'with an object' do
|
39
|
-
|
42
|
+
let(:options) { { object: :task } }
|
40
43
|
|
41
44
|
it 'generates a field name' do
|
42
|
-
expect(subject.field).to eq('
|
45
|
+
expect(subject.field).to eq('task_completed_at')
|
43
46
|
end
|
44
47
|
|
45
48
|
it 'generates a predicate name' do
|
46
|
-
expect(subject.predicate).to eq('
|
49
|
+
expect(subject.predicate).to eq('task_completed?')
|
47
50
|
end
|
48
51
|
|
49
52
|
it 'generates an inverse predicate name' do
|
50
|
-
expect(subject.inverse_predicate).to eq('
|
53
|
+
expect(subject.inverse_predicate).to eq('task_not_completed?')
|
51
54
|
end
|
52
55
|
|
53
56
|
it 'generates an action name' do
|
54
|
-
expect(subject.action).to eq('
|
57
|
+
expect(subject.action).to eq('complete_task!')
|
55
58
|
end
|
56
59
|
|
57
60
|
it 'generates a safe action name' do
|
58
|
-
expect(subject.safe_action).to eq('
|
61
|
+
expect(subject.safe_action).to eq('complete_task')
|
59
62
|
end
|
60
63
|
|
61
64
|
it 'generates a collective action name' do
|
62
|
-
expect(subject.collective_action).to eq('
|
65
|
+
expect(subject.collective_action).to eq('complete_all_tasks')
|
63
66
|
end
|
64
67
|
|
65
68
|
it 'generates a scope name' do
|
66
|
-
expect(subject.scope).to eq('
|
69
|
+
expect(subject.scope).to eq('task_completed')
|
67
70
|
end
|
68
71
|
|
69
72
|
it 'generates an inverse scope name' do
|
70
|
-
expect(subject.inverse_scope).to eq('
|
73
|
+
expect(subject.inverse_scope).to eq('task_not_completed')
|
71
74
|
end
|
72
75
|
end
|
73
76
|
|
74
77
|
context 'with a date field' do
|
75
|
-
|
78
|
+
let(:options) { { field_type: :date } }
|
76
79
|
|
77
80
|
it 'generates a field name' do
|
78
|
-
expect(subject.field).to eq('
|
81
|
+
expect(subject.field).to eq('completed_on')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with a custom field name' do
|
86
|
+
let(:options) { { field_name: :completion_time } }
|
87
|
+
|
88
|
+
it 'returns the field name' do
|
89
|
+
expect(subject.field).to eq('completion_time')
|
79
90
|
end
|
80
91
|
end
|
81
92
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe ActiveRecord::Events do
|
4
|
-
around
|
4
|
+
around(:each) do |example|
|
5
|
+
Timecop.freeze { example.run }
|
6
|
+
end
|
5
7
|
|
6
8
|
let!(:task) { create(:task) }
|
7
9
|
|
@@ -30,8 +32,19 @@ RSpec.describe ActiveRecord::Events do
|
|
30
32
|
expect(task.completed_at).to eq(Time.current)
|
31
33
|
end
|
32
34
|
|
35
|
+
context 'with a non-persisted object' do
|
36
|
+
it 'updates the timestamp' do
|
37
|
+
task = build(:task, completed_at: 3.days.ago)
|
38
|
+
|
39
|
+
task.complete!
|
40
|
+
|
41
|
+
expect(task.completed_at).to eq(Time.current)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
33
45
|
it 'records multiple timestamps at once' do
|
34
46
|
Task.complete_all
|
47
|
+
|
35
48
|
expect(task.reload).to be_completed
|
36
49
|
end
|
37
50
|
|
@@ -43,16 +56,6 @@ RSpec.describe ActiveRecord::Events do
|
|
43
56
|
expect(Task.not_completed).to include(task)
|
44
57
|
end
|
45
58
|
|
46
|
-
context 'with the skip scopes flag' do
|
47
|
-
it "doesn't define a scope" do
|
48
|
-
expect(Task).not_to respond_to(:archived)
|
49
|
-
end
|
50
|
-
|
51
|
-
it "doesn't define an inverse scope" do
|
52
|
-
expect(Task).not_to respond_to(:not_archived)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
59
|
it 'allows overriding instance methods' do
|
57
60
|
expect(ActiveRecord::Base.logger).to receive(:info)
|
58
61
|
|
@@ -68,4 +71,50 @@ RSpec.describe ActiveRecord::Events do
|
|
68
71
|
|
69
72
|
expect(task.reload).to be_completed
|
70
73
|
end
|
74
|
+
|
75
|
+
describe 'time comparison strategy' do
|
76
|
+
it 'defines a predicate method comparing against current time' do
|
77
|
+
task.update_columns(expired_at: 1.hour.from_now)
|
78
|
+
expect(task.expired?).to eq(false)
|
79
|
+
|
80
|
+
task.update_columns(expired_at: 1.hour.ago)
|
81
|
+
expect(task.expired?).to eq(true)
|
82
|
+
|
83
|
+
task.update_columns(expired_at: nil)
|
84
|
+
expect(task.expired?).to eq(false)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'defines an inverse predicate method comparing against current time' do
|
88
|
+
task.update_columns(expired_at: 1.hour.from_now)
|
89
|
+
expect(task.not_expired?).to eq(true)
|
90
|
+
|
91
|
+
task.update_columns(expired_at: 1.hour.ago)
|
92
|
+
expect(task.not_expired?).to eq(false)
|
93
|
+
|
94
|
+
task.update_columns(expired_at: nil)
|
95
|
+
expect(task.not_expired?).to eq(true)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'defines a scope comparing against current time' do
|
99
|
+
task.update_columns(expired_at: 1.hour.from_now)
|
100
|
+
expect(Task.expired).not_to include(task)
|
101
|
+
|
102
|
+
task.update_columns(expired_at: 1.hour.ago)
|
103
|
+
expect(Task.expired).to include(task)
|
104
|
+
|
105
|
+
task.update_columns(expired_at: nil)
|
106
|
+
expect(Task.expired).not_to include(task)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'defines an inverse scope comparing against current time' do
|
110
|
+
task.update_columns(expired_at: 1.hour.from_now)
|
111
|
+
expect(Task.not_expired).to include(task)
|
112
|
+
|
113
|
+
task.update_columns(expired_at: 1.hour.ago)
|
114
|
+
expect(Task.not_expired).not_to include(task)
|
115
|
+
|
116
|
+
task.update_columns(expired_at: nil)
|
117
|
+
expect(Task.not_expired).to include(task)
|
118
|
+
end
|
119
|
+
end
|
71
120
|
end
|
data/spec/dummy/config/boot.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# gem 'sqlite3'
|
6
6
|
development:
|
7
7
|
adapter: sqlite3
|
8
|
-
database:
|
8
|
+
database: db/development.sqlite3
|
9
9
|
pool: 5
|
10
10
|
timeout: 5000
|
11
11
|
|
@@ -14,12 +14,12 @@ development:
|
|
14
14
|
# Do not set this db to the same as development or production.
|
15
15
|
test:
|
16
16
|
adapter: sqlite3
|
17
|
-
database:
|
17
|
+
database: db/test.sqlite3
|
18
18
|
pool: 5
|
19
19
|
timeout: 5000
|
20
20
|
|
21
21
|
production:
|
22
22
|
adapter: sqlite3
|
23
|
-
database:
|
23
|
+
database: db/production.sqlite3
|
24
24
|
pool: 5
|
25
25
|
timeout: 5000
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -2,20 +2,21 @@
|
|
2
2
|
# of editing this file, please use the migrations feature of Active Record to
|
3
3
|
# incrementally modify your database, and then regenerate this schema definition.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# from scratch.
|
9
|
-
#
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
9
|
+
# migrations use external dependencies or application code.
|
10
10
|
#
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema.define(version:
|
13
|
+
ActiveRecord::Schema.define(version: 2022_06_09_015338) do
|
14
14
|
|
15
15
|
create_table "tasks", force: :cascade do |t|
|
16
16
|
t.datetime "completed_at"
|
17
17
|
t.datetime "created_at", null: false
|
18
18
|
t.datetime "updated_at", null: false
|
19
|
+
t.datetime "expired_at"
|
19
20
|
end
|
20
21
|
|
21
22
|
end
|
@@ -2,7 +2,13 @@ require 'spec_helper'
|
|
2
2
|
require 'generators/active_record/event/event_generator'
|
3
3
|
|
4
4
|
RSpec.describe ActiveRecord::Generators::EventGenerator, type: :generator do
|
5
|
-
arguments %w[
|
5
|
+
arguments %w[
|
6
|
+
task complete
|
7
|
+
--field-type=date
|
8
|
+
--skip-scopes
|
9
|
+
--strategy=time_comparison
|
10
|
+
]
|
11
|
+
|
6
12
|
destination File.expand_path('../../../../tmp', __dir__)
|
7
13
|
|
8
14
|
before { prepare_destination }
|
@@ -30,7 +36,7 @@ RSpec.describe ActiveRecord::Generators::EventGenerator, type: :generator do
|
|
30
36
|
|
31
37
|
assert_file 'app/models/task.rb', <<-RUBY.strip_heredoc
|
32
38
|
class Task < ActiveRecord::Base
|
33
|
-
has_event :complete, field_type: :date, skip_scopes: true
|
39
|
+
has_event :complete, field_type: :date, skip_scopes: true, strategy: :time_comparison
|
34
40
|
end
|
35
41
|
RUBY
|
36
42
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
ENV['RAILS_ENV'] ||= 'test'
|
2
2
|
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'simplecov'
|
5
|
+
|
6
|
+
SimpleCov.start do
|
7
|
+
if ENV['CI']
|
8
|
+
require 'simplecov-lcov'
|
9
|
+
|
10
|
+
SimpleCov::Formatter::LcovFormatter.config do |config|
|
11
|
+
config.report_with_single_file = true
|
12
|
+
config.single_report_path = 'coverage/lcov.info'
|
13
|
+
end
|
14
|
+
|
15
|
+
formatter SimpleCov::Formatter::LcovFormatter
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
3
19
|
require File.expand_path('dummy/config/environment.rb', __dir__)
|
4
20
|
|
5
21
|
require 'factory_girl'
|
@@ -10,9 +26,6 @@ require 'zonebie/rspec'
|
|
10
26
|
Dir["#{__dir__}/support/**/*.rb"].each { |f| require f }
|
11
27
|
|
12
28
|
RSpec.configure do |config|
|
13
|
-
config.color = true
|
14
|
-
config.order = :random
|
15
|
-
|
16
29
|
config.include FactoryGirl::Syntax::Methods
|
17
30
|
config.include GeneratorHelpers, type: :generator
|
18
31
|
end
|
File without changes
|
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record-events
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bartosz Pieńkowski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '4.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '4.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: verbs
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '2.
|
33
|
+
version: '2.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '2.
|
40
|
+
version: '2.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: appraisal
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,33 +109,47 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 0.50.0
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: simplecov
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
117
|
+
version: 0.16.1
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
124
|
+
version: 0.16.1
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: simplecov-lcov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.7.0
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.7.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: sqlite3
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
128
142
|
requirements:
|
129
143
|
- - "~>"
|
130
144
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
145
|
+
version: '1.3'
|
132
146
|
type: :development
|
133
147
|
prerelease: false
|
134
148
|
version_requirements: !ruby/object:Gem::Requirement
|
135
149
|
requirements:
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
152
|
+
version: '1.3'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
154
|
name: timecop
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -175,27 +189,29 @@ files:
|
|
175
189
|
- README.md
|
176
190
|
- Rakefile
|
177
191
|
- lib/active_record/events.rb
|
192
|
+
- lib/active_record/events/extension.rb
|
178
193
|
- lib/active_record/events/macro.rb
|
194
|
+
- lib/active_record/events/method_factory.rb
|
179
195
|
- lib/active_record/events/naming.rb
|
180
|
-
- lib/active_record/events/verbs.rb
|
181
196
|
- lib/active_record/events/version.rb
|
182
197
|
- lib/generators/active_record/event/USAGE
|
183
198
|
- lib/generators/active_record/event/event_generator.rb
|
184
199
|
- spec/active_record/events/macro_spec.rb
|
200
|
+
- spec/active_record/events/method_factory_spec.rb
|
185
201
|
- spec/active_record/events/naming_spec.rb
|
186
202
|
- spec/active_record/events_spec.rb
|
187
203
|
- spec/dummy/app/models/task.rb
|
188
204
|
- spec/dummy/config/boot.rb
|
189
205
|
- spec/dummy/config/database.yml
|
190
206
|
- spec/dummy/config/environment.rb
|
191
|
-
- spec/dummy/db/development.sqlite3
|
192
207
|
- spec/dummy/db/migrate/20150813132804_create_tasks.rb
|
208
|
+
- spec/dummy/db/migrate/20220609015338_add_expired_at_to_tasks.rb
|
193
209
|
- spec/dummy/db/schema.rb
|
194
|
-
- spec/dummy/db/test.sqlite3
|
195
210
|
- spec/generators/active_record/event/event_generator_spec.rb
|
196
211
|
- spec/spec_helper.rb
|
197
|
-
- spec/support/factories.rb
|
198
|
-
- spec/support/generator_helpers.rb
|
212
|
+
- spec/support/factories/task_factory.rb
|
213
|
+
- spec/support/helpers/generator_helpers.rb
|
214
|
+
- spec/support/matchers/have_method.rb
|
199
215
|
homepage: https://github.com/pienkowb/active_record-events
|
200
216
|
licenses:
|
201
217
|
- MIT
|
@@ -215,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
215
231
|
- !ruby/object:Gem::Version
|
216
232
|
version: '0'
|
217
233
|
requirements: []
|
218
|
-
rubygems_version: 3.
|
234
|
+
rubygems_version: 3.1.6
|
219
235
|
signing_key:
|
220
236
|
specification_version: 4
|
221
237
|
summary: Manage timestamps in ActiveRecord models
|
@@ -226,12 +242,13 @@ test_files:
|
|
226
242
|
- spec/dummy/config/database.yml
|
227
243
|
- spec/dummy/config/boot.rb
|
228
244
|
- spec/dummy/db/schema.rb
|
229
|
-
- spec/dummy/db/test.sqlite3
|
230
245
|
- spec/dummy/db/migrate/20150813132804_create_tasks.rb
|
231
|
-
- spec/dummy/db/
|
246
|
+
- spec/dummy/db/migrate/20220609015338_add_expired_at_to_tasks.rb
|
247
|
+
- spec/active_record/events/method_factory_spec.rb
|
232
248
|
- spec/active_record/events/naming_spec.rb
|
233
249
|
- spec/active_record/events/macro_spec.rb
|
234
250
|
- spec/active_record/events_spec.rb
|
235
|
-
- spec/support/
|
236
|
-
- spec/support/
|
251
|
+
- spec/support/factories/task_factory.rb
|
252
|
+
- spec/support/matchers/have_method.rb
|
253
|
+
- spec/support/helpers/generator_helpers.rb
|
237
254
|
- spec/generators/active_record/event/event_generator_spec.rb
|
Binary file
|
data/spec/dummy/db/test.sqlite3
DELETED
Binary file
|