active_record-events 4.0.1 → 4.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18d2fc6a8788d3190b5da3dd8dc45fbf307bb55344a6072ba24db67ff9b0e6cb
4
- data.tar.gz: 17767f7e00810c9717cc5fa4bd92d49303b22427eb5da06a3f27ea39f8a4fbf8
3
+ metadata.gz: 7ec4cd90a44c6c550a55b4fed0e50686380441cb86a4089a9716d8d5db350984
4
+ data.tar.gz: bf03cc5c153173f2bf842d240374e00e7a94957fcdd4b855dab0a00fc4999a12
5
5
  SHA512:
6
- metadata.gz: d39a25b8c5695aa8c81b9780744f0639ab2b0bac74296486721ec1c28cad0c67f801441b78c3faa48162d60938297a33638dada9e597a84a7c8c888c94cc86b2
7
- data.tar.gz: afb5395aba7821776ff9aa3790d0f04f625daa1a4de6495e9069cf210a4ea3023b7449bb6cce58e9eb158887a541da6f927b9ba03e61df0974b6cd473a6beacf
6
+ metadata.gz: 7c567f576431e641cd39dad28b71c033093b8cd51e368003ec93b1a44ff1177ac0272a257d1b73b2398072aeb4d962d568865bbd110e2116085d76b513c5da3a
7
+ data.tar.gz: 1b83157999bf3ad63a76475fcf4b011f0f95cd19d83792f1db2a9f5eaedcca178a69fcfccb01898f7d9452ed06d2ba1c28cac3bb0fe657f5e6ed7304b2db4dd2
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ActiveRecord::Events [![Gem version](https://img.shields.io/gem/v/active_record-events)](https://rubygems.org/gems/active_record-events) [![Build status](https://img.shields.io/travis/pienkowb/active_record-events/develop)](https://travis-ci.org/pienkowb/active_record-events) [![Coverage status](https://img.shields.io/coveralls/github/pienkowb/active_record-events/develop)](https://coveralls.io/github/pienkowb/active_record-events) [![Maintainability status](https://img.shields.io/codeclimate/maintainability/pienkowb/active_record-events)](https://codeclimate.com/github/pienkowb/active_record-events)
1
+ # ActiveRecord::Events [![Gem version](https://img.shields.io/gem/v/active_record-events)](https://rubygems.org/gems/active_record-events) [![Build status](https://img.shields.io/github/actions/workflow/status/pienkowb/active_record-events/test.yml?branch=develop)](https://github.com/pienkowb/active_record-events/actions/workflows/test.yml?query=branch%3Adevelop) [![Coverage status](https://img.shields.io/coveralls/github/pienkowb/active_record-events/develop)](https://coveralls.io/github/pienkowb/active_record-events) [![Maintainability status](https://img.shields.io/codeclimate/maintainability/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
 
@@ -119,6 +119,26 @@ has_event :complete, field_name: :completion_time
119
119
 
120
120
  Note that the `field_name` option takes precedence over the `field_type` option.
121
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.
125
+
126
+ You can do this with the `strategy` option, which can be either `presence` or `time_comparison`:
127
+
128
+ ```ruby
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
140
+ ```
141
+
122
142
  ### Specifying an object
123
143
 
124
144
  There are events which do not relate to a model itself but to one of its attributes – take the `User` model with the `email_confirmed_at` field as an example.
@@ -208,6 +228,8 @@ end
208
228
  - [Tomasz Skupiński](https://github.com/tskupinski)
209
229
  - [Oskar Janusz](https://github.com/oskaror)
210
230
  - [Mike Rogers](https://github.com/MikeRogers0)
231
+ - [Spencer Rogers](https://github.com/serogers)
232
+ - [Benno Bielmeier](https://github.com/bbenno)
211
233
 
212
234
  ## See also
213
235
 
@@ -6,11 +6,13 @@ module ActiveRecord
6
6
  def initialize(event_name, options)
7
7
  @options = options
8
8
  @naming = Naming.new(event_name, options)
9
+ @strategy = options[:strategy].try(:to_sym)
10
+ @field_type = options[:field_type].try(:to_sym)
9
11
  end
10
12
 
11
13
  def instance_methods
12
14
  Module.new.tap do |module_|
13
- define_predicate_method(module_, naming)
15
+ define_predicate_method(module_, naming, strategy)
14
16
  define_inverse_predicate_method(module_, naming)
15
17
 
16
18
  define_action_method(module_, naming)
@@ -20,11 +22,13 @@ module ActiveRecord
20
22
 
21
23
  def class_methods
22
24
  Module.new.tap do |module_|
23
- define_collective_action_method(module_, naming)
25
+ time_class = field_type == :date ? Date : Time
26
+
27
+ define_collective_action_method(module_, naming, time_class)
24
28
 
25
29
  unless options[:skip_scopes]
26
- define_scope_method(module_, naming)
27
- define_inverse_scope_method(module_, naming)
30
+ define_scope_method(module_, naming, strategy, time_class)
31
+ define_inverse_scope_method(module_, naming, strategy, time_class)
28
32
  end
29
33
  end
30
34
  end
@@ -33,10 +37,16 @@ module ActiveRecord
33
37
 
34
38
  attr_reader :options
35
39
  attr_reader :naming
40
+ attr_reader :strategy
41
+ attr_reader :field_type
36
42
 
37
- def define_predicate_method(module_, naming)
43
+ def define_predicate_method(module_, naming, strategy)
38
44
  module_.send(:define_method, naming.predicate) do
39
- self[naming.field].present?
45
+ if strategy == :time_comparison
46
+ self[naming.field].present? && !self[naming.field].future?
47
+ else
48
+ self[naming.field].present?
49
+ end
40
50
  end
41
51
  end
42
52
 
@@ -62,25 +72,35 @@ module ActiveRecord
62
72
  end
63
73
  end
64
74
 
65
- def define_collective_action_method(module_, naming)
75
+ def define_collective_action_method(module_, naming, time_class)
66
76
  module_.send(:define_method, naming.collective_action) do
67
77
  if respond_to?(:touch_all)
68
78
  touch_all(naming.field)
69
79
  else
70
- update_all(naming.field => Time.current)
80
+ update_all(naming.field => time_class.current)
71
81
  end
72
82
  end
73
83
  end
74
84
 
75
- def define_scope_method(module_, naming)
85
+ def define_scope_method(module_, naming, strategy, time_class)
76
86
  module_.send(:define_method, naming.scope) do
77
- where(arel_table[naming.field].not_eq(nil))
87
+ if strategy == :time_comparison
88
+ where(arel_table[naming.field].lteq(time_class.current))
89
+ else
90
+ where(arel_table[naming.field].not_eq(nil))
91
+ end
78
92
  end
79
93
  end
80
94
 
81
- def define_inverse_scope_method(module_, naming)
95
+ def define_inverse_scope_method(module_, naming, strategy, time_class)
82
96
  module_.send(:define_method, naming.inverse_scope) do
83
- where(arel_table[naming.field].eq(nil))
97
+ arel_field = arel_table[naming.field]
98
+
99
+ if strategy == :time_comparison
100
+ where(arel_field.eq(nil).or(arel_field.gt(time_class.current)))
101
+ else
102
+ where(arel_field.eq(nil))
103
+ end
84
104
  end
85
105
  end
86
106
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Events
3
- VERSION = '4.0.1'.freeze
3
+ VERSION = '4.1.1'.freeze
4
4
  end
5
5
  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: /class.+\n/
45
+ inject_into_file model_file_path, "\s\s#{macro}\n", after: pattern
43
46
  end
44
47
 
45
48
  private
@@ -71,4 +71,108 @@ RSpec.describe ActiveRecord::Events do
71
71
 
72
72
  expect(task.reload).to be_completed
73
73
  end
74
+
75
+ describe 'time comparison strategy' do
76
+ it 'defines a predicate method comparing against current time' do
77
+ task.update!(expired_at: 1.hour.from_now)
78
+ expect(task.expired?).to eq(false)
79
+
80
+ task.update!(expired_at: 1.hour.ago)
81
+ expect(task.expired?).to eq(true)
82
+
83
+ task.update!(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!(expired_at: 1.hour.from_now)
89
+ expect(task.not_expired?).to eq(true)
90
+
91
+ task.update!(expired_at: 1.hour.ago)
92
+ expect(task.not_expired?).to eq(false)
93
+
94
+ task.update!(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!(expired_at: 1.hour.from_now)
100
+ expect(Task.expired).not_to include(task)
101
+
102
+ task.update!(expired_at: 1.hour.ago)
103
+ expect(Task.expired).to include(task)
104
+
105
+ task.update!(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!(expired_at: 1.hour.from_now)
111
+ expect(Task.not_expired).to include(task)
112
+
113
+ task.update!(expired_at: 1.hour.ago)
114
+ expect(Task.not_expired).not_to include(task)
115
+
116
+ task.update!(expired_at: nil)
117
+ expect(Task.not_expired).to include(task)
118
+ end
119
+
120
+ context 'with a date field' do
121
+ it 'defines a predicate method comparing against current date' do
122
+ task.update!(notified_on: Date.yesterday)
123
+ expect(task.notified?).to eq(true)
124
+
125
+ task.update!(notified_on: Date.today)
126
+ expect(task.notified?).to eq(true)
127
+
128
+ task.update!(notified_on: Date.tomorrow)
129
+ expect(task.notified?).to eq(false)
130
+
131
+ task.update!(notified_on: nil)
132
+ expect(task.notified?).to eq(false)
133
+ end
134
+
135
+ it 'defines an inverse predicate method comparing against current date' do
136
+ task.update!(notified_on: Date.yesterday)
137
+ expect(task.not_notified?).to eq(false)
138
+
139
+ task.update!(notified_on: Date.today)
140
+ expect(task.not_notified?).to eq(false)
141
+
142
+ task.update!(notified_on: Date.tomorrow)
143
+ expect(task.not_notified?).to eq(true)
144
+
145
+ task.update!(notified_on: nil)
146
+ expect(task.not_notified?).to eq(true)
147
+ end
148
+
149
+ it 'defines a scope comparing against current date' do
150
+ task.update!(notified_on: Date.yesterday)
151
+ expect(Task.notified).to include(task)
152
+
153
+ task.update!(notified_on: Date.today)
154
+ expect(Task.notified).to include(task)
155
+
156
+ task.update!(notified_on: Date.tomorrow)
157
+ expect(Task.notified).not_to include(task)
158
+
159
+ task.update!(notified_on: nil)
160
+ expect(Task.notified).not_to include(task)
161
+ end
162
+
163
+ it 'defines an inverse scope comparing against current date' do
164
+ task.update!(notified_on: Date.yesterday)
165
+ expect(Task.not_notified).not_to include(task)
166
+
167
+ task.update!(notified_on: Date.today)
168
+ expect(Task.not_notified).not_to include(task)
169
+
170
+ task.update!(notified_on: Date.tomorrow)
171
+ expect(Task.not_notified).to include(task)
172
+
173
+ task.update!(notified_on: nil)
174
+ expect(Task.not_notified).to include(task)
175
+ end
176
+ end
177
+ end
74
178
  end
@@ -1,6 +1,8 @@
1
1
  class Task < ActiveRecord::Base
2
2
  has_event :complete
3
- has_events :archive, skip_scopes: true
3
+ has_event :archive, skip_scopes: true
4
+ has_event :expire, strategy: :time_comparison
5
+ has_event :notify, field_type: :date, strategy: :time_comparison
4
6
 
5
7
  def complete!
6
8
  super
@@ -0,0 +1,5 @@
1
+ class AddExpiredAtToTasks < ACTIVE_RECORD_MIGRATION_CLASS
2
+ def change
3
+ add_column :tasks, :expired_at, :datetime
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddNotifiedOnToTasks < ACTIVE_RECORD_MIGRATION_CLASS
2
+ def change
3
+ add_column :tasks, :notified_on, :date
4
+ end
5
+ end
@@ -2,20 +2,22 @@
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
- # Note that this schema.rb definition is the authoritative source for your
6
- # database schema. If you need to create the application database on another
7
- # system, you should be using db:schema:load, not running all the migrations
8
- # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
- # you'll amass, the slower it'll run and the greater likelihood for issues).
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: 2015_08_13_132804) do
13
+ ActiveRecord::Schema.define(version: 2023_03_08_160359) 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"
20
+ t.date "notified_on"
19
21
  end
20
22
 
21
23
  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[task complete --field-type=date --skip-scopes]
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,9 +1,20 @@
1
1
  ENV['RAILS_ENV'] ||= 'test'
2
2
 
3
3
  require 'bundler/setup'
4
- require 'coveralls'
4
+ require 'simplecov'
5
5
 
6
- Coveralls.wear!
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
7
18
 
8
19
  require File.expand_path('dummy/config/environment.rb', __dir__)
9
20
 
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.0.1
4
+ version: 4.1.1
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: 2021-03-15 00:00:00.000000000 Z
11
+ date: 2023-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.2'
55
- - !ruby/object:Gem::Dependency
56
- name: coveralls
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '0.8'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '0.8'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: factory_girl
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +108,34 @@ dependencies:
122
108
  - - "~>"
123
109
  - !ruby/object:Gem::Version
124
110
  version: 0.50.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.16.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.16.1
125
+ - !ruby/object:Gem::Dependency
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
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: sqlite3
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -191,6 +205,8 @@ files:
191
205
  - spec/dummy/config/database.yml
192
206
  - spec/dummy/config/environment.rb
193
207
  - spec/dummy/db/migrate/20150813132804_create_tasks.rb
208
+ - spec/dummy/db/migrate/20220609015338_add_expired_at_to_tasks.rb
209
+ - spec/dummy/db/migrate/20230308160359_add_notified_on_to_tasks.rb
194
210
  - spec/dummy/db/schema.rb
195
211
  - spec/generators/active_record/event/event_generator_spec.rb
196
212
  - spec/spec_helper.rb
@@ -216,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
216
232
  - !ruby/object:Gem::Version
217
233
  version: '0'
218
234
  requirements: []
219
- rubygems_version: 3.0.3
235
+ rubygems_version: 3.1.6
220
236
  signing_key:
221
237
  specification_version: 4
222
238
  summary: Manage timestamps in ActiveRecord models
@@ -228,6 +244,8 @@ test_files:
228
244
  - spec/dummy/config/boot.rb
229
245
  - spec/dummy/db/schema.rb
230
246
  - spec/dummy/db/migrate/20150813132804_create_tasks.rb
247
+ - spec/dummy/db/migrate/20220609015338_add_expired_at_to_tasks.rb
248
+ - spec/dummy/db/migrate/20230308160359_add_notified_on_to_tasks.rb
231
249
  - spec/active_record/events/method_factory_spec.rb
232
250
  - spec/active_record/events/naming_spec.rb
233
251
  - spec/active_record/events/macro_spec.rb