active_record-events 2.2.0 → 3.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 925ad39a33ff672195121c743c130982bf379388a98dfb9fddefe4a624ce0d3a
4
- data.tar.gz: c428f4b9ea6a9c44b5c472c80a5bd4349eaab9337b67209d58e7238325824b72
3
+ metadata.gz: 4d3c49af800cff40043af18ec6357dd41a8492a75942525701fa607b078a0bb0
4
+ data.tar.gz: 01b3d06c56050a764fa67b772e8fdf6f791603be7e220773d7e7547a53450ef6
5
5
  SHA512:
6
- metadata.gz: a5eadfba86bf1b157fcf1906628c3e391b79677524e9851374cedd764b7170867f933781ddb6ee87f435c67b872171e471dccd57904b65700c4534c18a005815
7
- data.tar.gz: ba34b744b02a293db01f51dbad13cac7c5afc687aaf4e8ee4d3ac533981d121ba46b8b7099e789b0df26022e5383d788e1c098ca581780c2430b540f6d012d0a
6
+ metadata.gz: a3f18c26a27d1eca9ee0517ea19dbf087019200e02fa02b95f0412c4901f4a2aec4239115fc38191d1e6ad7f643067f1567fde10633ace0beb71ddb4623f027b
7
+ data.tar.gz: a388c51a55a45a13bccc7aad2288aefbef85c49ba322f6bd7fe0791aa4f378b1be1e41ac85562c732168be41553499560e1f8d79a2e0ea2b4691d25980189087
File without changes
data/README.md CHANGED
@@ -24,21 +24,24 @@ $ gem install active_record-events
24
24
 
25
25
  ## Usage
26
26
 
27
- Recording a timestamp in order to mark that an event occurred to an object is a common practice when dealing with ActiveRecord models – `created_at` and `updated_at` fields handled by ActiveRecord itself are a good example of such approach.
27
+ Recording a timestamp in order to mark that an event occurred to an object is a common practice when dealing with ActiveRecord models.
28
+ A good example of such an approach is how ActiveRecord handles the `created_at` and `updated_at` fields.
28
29
  This gem allows you to manage custom timestamp fields in the exact same manner.
29
30
 
31
+ ### Example
32
+
30
33
  Consider a `Task` model with a `completed_at` field and the following methods:
31
34
 
32
35
  ```ruby
33
36
  class Task < ActiveRecord::Base
34
- def not_completed?
35
- !completed?
36
- end
37
-
38
37
  def completed?
39
38
  completed_at.present?
40
39
  end
41
40
 
41
+ def not_completed?
42
+ !completed?
43
+ end
44
+
42
45
  def complete
43
46
  complete! if not_completed?
44
47
  end
@@ -53,7 +56,7 @@ class Task < ActiveRecord::Base
53
56
  end
54
57
  ```
55
58
 
56
- Instead of defining these five methods explicitly, you can use a macro provided by the gem.
59
+ Instead of defining all of these methods by hand, you can use the `has_event` macro provided by the gem.
57
60
 
58
61
  ```ruby
59
62
  class Task < ActiveRecord::Base
@@ -61,12 +64,10 @@ class Task < ActiveRecord::Base
61
64
  end
62
65
  ```
63
66
 
64
- This approach is very efficient when more than one field has to be handled that way.
65
- In such a case, many lines of code can be replaced with an expressive one-liner.
67
+ As a result, the methods will be generated automatically.
66
68
 
67
- ```ruby
68
- has_events :complete, :archive
69
- ```
69
+ *It's important to note that the `completed_at` column has to already exist in the database.*
70
+ *Consider [using the generator](#using-a-rails-generator) to create a necessary migration.*
70
71
 
71
72
  ### Scopes
72
73
 
@@ -77,7 +78,30 @@ scope :not_completed, -> { where(completed_at: nil) }
77
78
  scope :completed, -> { where.not(completed_at: nil) }
78
79
  ```
79
80
 
80
- ### Object
81
+ The inclusion of scope methods can be omitted by passing the `skip_scopes` flag.
82
+
83
+ ```ruby
84
+ has_event :complete, skip_scopes: true
85
+ ```
86
+
87
+ ### Multiple events
88
+
89
+ Using the macro is efficient when more than one field has to be handled that way.
90
+ In such a case, many lines of code can be replaced with an expressive one-liner.
91
+
92
+ ```ruby
93
+ has_events :complete, :archive
94
+ ```
95
+
96
+ ### Setting a field type
97
+
98
+ In case of date fields, which by convention have names ending with `_on` instead of `_at` (e.g. `delivered_on`), the `field_type` option needs to be passed to the macro:
99
+
100
+ ```ruby
101
+ has_event :deliver, field_type: :date
102
+ ```
103
+
104
+ ### Specifying an object
81
105
 
82
106
  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.
83
107
  In order to keep method names grammatically correct, you can specify an object using the `object` option.
@@ -101,6 +125,71 @@ As well as these two scopes:
101
125
  - `email_confirmed`
102
126
  - `email_not_confirmed`
103
127
 
128
+ ### Using a Rails generator
129
+
130
+ If you want to quickly add a new event, you can make use of a Rails generator provided by the gem:
131
+
132
+ ```
133
+ $ rails generate active_record:event task complete
134
+ ```
135
+
136
+ It will create a necessary migration and insert a `has_event` statement into the model class.
137
+
138
+ ```ruby
139
+ # db/migrate/XXX_add_completed_at_to_tasks.rb
140
+
141
+ class AddCompletedAtToTasks < ActiveRecord::Migration[6.0]
142
+ def change
143
+ add_column :tasks, :completed_at, :datetime
144
+ end
145
+ end
146
+ ```
147
+
148
+ ```ruby
149
+ # app/models/task.rb
150
+
151
+ class Task < ActiveRecord::Base
152
+ has_event :complete
153
+ end
154
+ ```
155
+
156
+ All of the macro options are supported by the generator and can be passed via the command line.
157
+ For instance:
158
+
159
+ ```
160
+ $ rails generate active_record:event user confirm --object=email --skip-scopes
161
+ ```
162
+
163
+ For more information, run the generator with the `--help` option.
164
+
165
+ ### Overriding methods
166
+
167
+ If there's a need to override any of the methods generated by the macro, you can define a new method with the same name in the corresponding model class.
168
+ This applies to instance methods as well as class methods.
169
+ In both cases, the `super` keyword invokes the original method.
170
+
171
+ ```ruby
172
+ class Task < ActiveRecord::Base
173
+ has_event :complete
174
+
175
+ def complete!
176
+ super
177
+ logger.info("Task #{id} has been completed")
178
+ end
179
+
180
+ def self.complete_all
181
+ super
182
+ logger.info('All tasks have been completed')
183
+ end
184
+ end
185
+ ```
186
+
187
+ ## Contributors
188
+
189
+ - [Bartosz Pieńkowski](https://github.com/pienkowb)
190
+ - [Tomasz Skupiński](https://github.com/tskupinski)
191
+ - [Oskar Janusz](https://github.com/oskaror)
192
+
104
193
  ## See also
105
194
 
106
- - [ActiveRecord::Enum](http://api.rubyonrails.org/classes/ActiveRecord/Enum.html)
195
+ - [ActiveRecord::Enum](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html)
data/Rakefile CHANGED
@@ -26,11 +26,12 @@ require 'standalone_migrations'
26
26
 
27
27
  StandaloneMigrations::Tasks.load_tasks
28
28
 
29
- if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new('5.1.0')
30
- ACTIVE_RECORD_MIGRATION_CLASS = ActiveRecord::Migration[4.2]
31
- else
32
- ACTIVE_RECORD_MIGRATION_CLASS = ActiveRecord::Migration
33
- end
29
+ ACTIVE_RECORD_MIGRATION_CLASS =
30
+ if ActiveRecord::VERSION::MAJOR >= 5
31
+ ActiveRecord::Migration[4.2]
32
+ else
33
+ ActiveRecord::Migration
34
+ end
34
35
 
35
36
  Bundler::GemHelper.install_tasks
36
37
 
@@ -11,33 +11,43 @@ module ActiveRecord
11
11
  def has_event(name, options = {})
12
12
  naming = Naming.new(name, options)
13
13
 
14
- define_method("#{naming.predicate}?") do
15
- self[naming.field].present?
16
- end
17
-
18
- define_method("#{naming.inverse_predicate}?") do
19
- self[naming.field].blank?
20
- end
21
-
22
- define_method(naming.action) do
23
- touch(naming.field) if self[naming.field].blank?
24
- end
25
-
26
- define_method("#{naming.action}!") do
27
- touch(naming.field)
28
- end
29
-
30
- define_singleton_method(naming.collective_action) do
31
- update_all(naming.field => Time.current)
32
- end
33
-
34
- define_singleton_method(naming.scope) do
35
- where(arel_table[naming.field].not_eq(nil))
36
- end
37
-
38
- define_singleton_method(naming.inverse_scope) do
39
- where(arel_table[naming.field].eq(nil))
40
- end
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
+
23
+ define_method(naming.action) do
24
+ touch(naming.field)
25
+ end
26
+
27
+ define_method(naming.safe_action) do
28
+ __send__(naming.action) if __send__(naming.inverse_predicate)
29
+ end
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
45
+
46
+ define_method(naming.inverse_scope) do
47
+ where(arel_table[naming.field].eq(nil))
48
+ end
49
+ end
50
+ end)
41
51
  end
42
52
  end
43
53
  end
@@ -0,0 +1,36 @@
1
+ module ActiveRecord
2
+ module Events
3
+ class Macro
4
+ def initialize(event_name, options)
5
+ @event_name = event_name.to_s
6
+ @options = options
7
+ end
8
+
9
+ def to_s
10
+ "has_event :#{event_name}#{options_list}"
11
+ end
12
+
13
+ private
14
+
15
+ def event_name
16
+ @event_name.underscore
17
+ end
18
+
19
+ def options_list
20
+ options.unshift('').join(', ') if options.present?
21
+ end
22
+
23
+ def options
24
+ @options.map { |k, v| "#{k}: #{convert_value(v)}" }
25
+ end
26
+
27
+ def convert_value(value)
28
+ symbol_or_string?(value) ? ":#{value}" : value
29
+ end
30
+
31
+ def symbol_or_string?(value)
32
+ value.is_a?(Symbol) || value.is_a?(String)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,4 +1,4 @@
1
- require 'verbs'
1
+ require 'active_record/events/verbs'
2
2
 
3
3
  module ActiveRecord
4
4
  module Events
@@ -6,44 +6,58 @@ module ActiveRecord
6
6
  def initialize(infinitive, options = {})
7
7
  @infinitive = infinitive
8
8
  @object = options[:object].presence
9
+ @field_type = options[:field_type].try(:to_sym)
9
10
  end
10
11
 
11
12
  def field
12
- [@object, past_participle, 'at'].compact.join('_')
13
+ suffix = field_type == :date ? 'on' : 'at'
14
+ concatenate(object, past_participle, suffix)
13
15
  end
14
16
 
15
17
  def predicate
16
- [@object, past_participle].compact.join('_')
18
+ concatenate(object, past_participle) + '?'
17
19
  end
18
20
 
19
21
  def inverse_predicate
20
- [@object, 'not', past_participle].compact.join('_')
22
+ concatenate(object, 'not', past_participle) + '?'
21
23
  end
22
24
 
23
25
  def action
24
- [@infinitive, @object].compact.join('_')
26
+ concatenate(infinitive, object) + '!'
27
+ end
28
+
29
+ def safe_action
30
+ concatenate(infinitive, object)
25
31
  end
26
32
 
27
33
  def collective_action
28
- [@infinitive, 'all', pluralized_object].compact.join('_')
34
+ concatenate(infinitive, 'all', plural_object)
29
35
  end
30
36
 
31
37
  def scope
32
- [@object, past_participle].compact.join('_')
38
+ concatenate(object, past_participle)
33
39
  end
34
40
 
35
41
  def inverse_scope
36
- [@object, 'not', past_participle].compact.join('_')
42
+ concatenate(object, 'not', past_participle)
37
43
  end
38
44
 
39
45
  private
40
46
 
47
+ attr_reader :infinitive
48
+ attr_reader :object
49
+ attr_reader :field_type
50
+
51
+ def concatenate(*parts)
52
+ parts.compact.join('_')
53
+ end
54
+
41
55
  def past_participle
42
- @infinitive.verb.conjugate(tense: :past, aspect: :perfective)
56
+ infinitive.verb.conjugate(tense: :past, aspect: :perfective)
43
57
  end
44
58
 
45
- def pluralized_object
46
- @object.to_s.pluralize if @object.present?
59
+ def plural_object
60
+ object.to_s.pluralize if object.present?
47
61
  end
48
62
  end
49
63
  end
@@ -0,0 +1,5 @@
1
+ require 'verbs'
2
+
3
+ Verbs::Conjugator.conjugations do |conjugate|
4
+ conjugate.single_terminal_consonant :deliver
5
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Events
3
- VERSION = '2.2.0'
3
+ VERSION = '3.0.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,15 @@
1
+ Description:
2
+ Generates a database migration and updates the model file (if it exists).
3
+ Pass the model name, either CamelCased or under_scored, and the event name
4
+ as an infinitive.
5
+
6
+ Examples:
7
+ `bin/rails generate active_record:event task complete`
8
+
9
+ Generates a migration adding a `completed_at` column to the `tasks`
10
+ table and updates the Task model.
11
+
12
+ `bin/rails generate active_record:event user confirm --object=email`
13
+
14
+ Generates a migration adding an `email_confirmed_at` column to the
15
+ `users` table and updates the User model.
@@ -0,0 +1,60 @@
1
+ require 'rails/generators'
2
+
3
+ require 'active_record/events/naming'
4
+ require 'active_record/events/macro'
5
+
6
+ module ActiveRecord
7
+ module Generators
8
+ class EventGenerator < Rails::Generators::Base
9
+ MACRO_OPTIONS = %w[object field_type skip_scopes].freeze
10
+
11
+ argument :model_name, type: :string
12
+ argument :event_name, type: :string
13
+
14
+ class_option :skip_scopes, type: :boolean,
15
+ desc: 'Skip the inclusion of scope methods'
16
+ class_option :field_type, type: :string,
17
+ desc: 'The field type (datetime or date)'
18
+ class_option :object, type: :string,
19
+ desc: 'The name of the object'
20
+
21
+ source_root File.expand_path('templates', __dir__)
22
+
23
+ def generate_migration_file
24
+ naming = ActiveRecord::Events::Naming.new(event_name, options)
25
+
26
+ table_name = model_name.tableize
27
+ field_name = naming.field
28
+ field_type = options[:field_type] || 'datetime'
29
+
30
+ migration_name = "add_#{field_name}_to_#{table_name}"
31
+ attributes = "#{field_name}:#{field_type}"
32
+
33
+ invoke 'active_record:migration', [migration_name, attributes]
34
+ end
35
+
36
+ def update_model_file
37
+ return unless model_file_exists?
38
+
39
+ macro_options = options.slice(*MACRO_OPTIONS)
40
+ macro = ActiveRecord::Events::Macro.new(event_name, macro_options)
41
+
42
+ inject_into_file model_file_path, "\s\s#{macro}\n", after: /class.+\n/
43
+ end
44
+
45
+ private
46
+
47
+ def model_file_exists?
48
+ File.exist?(model_file_path)
49
+ end
50
+
51
+ def model_file_path
52
+ File.expand_path("app/models/#{model_file_name}", destination_root)
53
+ end
54
+
55
+ def model_file_name
56
+ "#{model_name.underscore.singularize}.rb"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'active_record/events/macro'
3
+
4
+ RSpec.describe ActiveRecord::Events::Macro do
5
+ let(:event_name) { :confirm }
6
+
7
+ subject { described_class.new(event_name, options) }
8
+
9
+ context 'without options' do
10
+ let(:options) { {} }
11
+
12
+ it "doesn't include any options" do
13
+ expect(subject.to_s).to eq('has_event :confirm')
14
+ end
15
+ end
16
+
17
+ context 'with a string option' do
18
+ let(:options) { { object: 'email' } }
19
+
20
+ it 'prepends the option value with a colon' do
21
+ expect(subject.to_s).to eq('has_event :confirm, object: :email')
22
+ end
23
+ end
24
+
25
+ context 'with a symbol option' do
26
+ let(:options) { { object: :email } }
27
+
28
+ it 'prepends the option value with a colon' do
29
+ expect(subject.to_s).to eq('has_event :confirm, object: :email')
30
+ end
31
+ end
32
+
33
+ context 'with a boolean option' do
34
+ let(:options) { { skip_scopes: true } }
35
+
36
+ it "doesn't prepend the option value with a colon" do
37
+ expect(subject.to_s).to eq('has_event :confirm, skip_scopes: true')
38
+ end
39
+ end
40
+
41
+ context 'with multiple options' do
42
+ let(:options) { { object: :email, field_type: :date } }
43
+
44
+ it 'includes all of the options' do
45
+ macro = 'has_event :confirm, object: :email, field_type: :date'
46
+ expect(subject.to_s).to eq(macro)
47
+ end
48
+ end
49
+ end
@@ -1,36 +1,38 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe ActiveRecord::Events::Naming do
4
- context 'without an object' do
5
- subject { described_class.new(:complete) }
4
+ subject { described_class.new(:complete) }
6
5
 
7
- it 'generates a field name' do
8
- expect(subject.field).to eq('completed_at')
9
- end
6
+ it 'generates a field name' do
7
+ expect(subject.field).to eq('completed_at')
8
+ end
10
9
 
11
- it 'generates a predicate name' do
12
- expect(subject.predicate).to eq('completed')
13
- end
10
+ it 'generates a predicate name' do
11
+ expect(subject.predicate).to eq('completed?')
12
+ end
14
13
 
15
- it 'generates an inverse predicate name' do
16
- expect(subject.inverse_predicate).to eq('not_completed')
17
- end
14
+ it 'generates an inverse predicate name' do
15
+ expect(subject.inverse_predicate).to eq('not_completed?')
16
+ end
18
17
 
19
- it 'generates an action name' do
20
- expect(subject.action).to eq('complete')
21
- end
18
+ it 'generates an action name' do
19
+ expect(subject.action).to eq('complete!')
20
+ end
22
21
 
23
- it 'generates a collective action name' do
24
- expect(subject.collective_action).to eq('complete_all')
25
- end
22
+ it 'generates a safe action name' do
23
+ expect(subject.safe_action).to eq('complete')
24
+ end
26
25
 
27
- it 'generates a scope name' do
28
- expect(subject.scope).to eq('completed')
29
- end
26
+ it 'generates a collective action name' do
27
+ expect(subject.collective_action).to eq('complete_all')
28
+ end
30
29
 
31
- it 'generates an inverse scope name' do
32
- expect(subject.inverse_scope).to eq('not_completed')
33
- end
30
+ it 'generates a scope name' do
31
+ expect(subject.scope).to eq('completed')
32
+ end
33
+
34
+ it 'generates an inverse scope name' do
35
+ expect(subject.inverse_scope).to eq('not_completed')
34
36
  end
35
37
 
36
38
  context 'with an object' do
@@ -41,15 +43,19 @@ RSpec.describe ActiveRecord::Events::Naming do
41
43
  end
42
44
 
43
45
  it 'generates a predicate name' do
44
- expect(subject.predicate).to eq('email_confirmed')
46
+ expect(subject.predicate).to eq('email_confirmed?')
45
47
  end
46
48
 
47
49
  it 'generates an inverse predicate name' do
48
- expect(subject.inverse_predicate).to eq('email_not_confirmed')
50
+ expect(subject.inverse_predicate).to eq('email_not_confirmed?')
49
51
  end
50
52
 
51
53
  it 'generates an action name' do
52
- expect(subject.action).to eq('confirm_email')
54
+ expect(subject.action).to eq('confirm_email!')
55
+ end
56
+
57
+ it 'generates a safe action name' do
58
+ expect(subject.safe_action).to eq('confirm_email')
53
59
  end
54
60
 
55
61
  it 'generates a collective action name' do
@@ -64,4 +70,12 @@ RSpec.describe ActiveRecord::Events::Naming do
64
70
  expect(subject.inverse_scope).to eq('email_not_confirmed')
65
71
  end
66
72
  end
73
+
74
+ context 'with a date field' do
75
+ subject { described_class.new(:deliver, field_type: :date) }
76
+
77
+ it 'generates a field name' do
78
+ expect(subject.field).to eq('delivered_on')
79
+ end
80
+ end
67
81
  end
@@ -1,69 +1,71 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe ActiveRecord::Events do
4
- around(:each) { |e| Timecop.freeze { e.run } }
4
+ around { |e| Timecop.freeze { e.run } }
5
5
 
6
- context 'without an object' do
7
- let!(:task) { create(:task) }
6
+ let!(:task) { create(:task) }
8
7
 
9
- it 'records a timestamp' do
10
- task.complete
8
+ it 'records a timestamp' do
9
+ task.complete
11
10
 
12
- expect(task.completed?).to eq(true)
13
- expect(task.not_completed?).to eq(false)
11
+ expect(task.completed?).to eq(true)
12
+ expect(task.not_completed?).to eq(false)
14
13
 
15
- expect(task.completed_at).to eq(Time.current)
16
- end
14
+ expect(task.completed_at).to eq(Time.current)
15
+ end
17
16
 
18
- it 'preserves a timestamp' do
19
- task = create(:task, completed_at: 3.days.ago)
20
- task.complete
17
+ it 'preserves a timestamp' do
18
+ task = create(:task, completed_at: 3.days.ago)
21
19
 
22
- expect(task.completed_at).to eq(3.days.ago)
23
- end
20
+ task.complete
24
21
 
25
- it 'updates a timestamp' do
26
- task = create(:task, completed_at: 3.days.ago)
27
- task.complete!
22
+ expect(task.completed_at).to eq(3.days.ago)
23
+ end
28
24
 
29
- expect(task.completed_at).to eq(Time.current)
30
- end
25
+ it 'updates a timestamp' do
26
+ task = create(:task, completed_at: 3.days.ago)
31
27
 
32
- it 'records multiple timestamps' do
33
- Task.complete_all
34
- expect(task.reload.completed?).to eq(true)
35
- end
28
+ task.complete!
36
29
 
37
- it 'defines a scope' do
38
- expect(Task.completed).not_to include(task)
39
- end
30
+ expect(task.completed_at).to eq(Time.current)
31
+ end
40
32
 
41
- it 'defines an inverse scope' do
42
- expect(Task.not_completed).to include(task)
43
- end
33
+ it 'records multiple timestamps at once' do
34
+ Task.complete_all
35
+ expect(task.reload).to be_completed
44
36
  end
45
37
 
46
- context 'with an object' do
47
- let!(:user) { create(:user) }
38
+ it 'defines a scope' do
39
+ expect(Task.completed).not_to include(task)
40
+ end
48
41
 
49
- it 'records a timestamp' do
50
- user.confirm_email
42
+ it 'defines an inverse scope' do
43
+ expect(Task.not_completed).to include(task)
44
+ end
51
45
 
52
- expect(user.email_confirmed?).to eq(true)
53
- expect(user.email_not_confirmed?).to eq(false)
46
+ context 'with the skip scopes flag' do
47
+ it "doesn't define a scope" do
48
+ expect(Task).not_to respond_to(:archived)
54
49
  end
55
50
 
56
- it 'records multiple timestamps' do
57
- User.confirm_all_emails
58
- expect(user.reload.email_confirmed?).to eq(true)
51
+ it "doesn't define an inverse scope" do
52
+ expect(Task).not_to respond_to(:not_archived)
59
53
  end
54
+ end
60
55
 
61
- it 'defines a scope' do
62
- expect(User.email_confirmed).not_to include(user)
63
- end
56
+ it 'allows overriding instance methods' do
57
+ expect(ActiveRecord::Base.logger).to receive(:info)
64
58
 
65
- it 'defines an inverse scope' do
66
- expect(User.email_not_confirmed).to include(user)
67
- end
59
+ task.complete!
60
+
61
+ expect(task).to be_completed
62
+ end
63
+
64
+ it 'allows overriding class methods' do
65
+ expect(ActiveRecord::Base.logger).to receive(:info)
66
+
67
+ Task.complete_all
68
+
69
+ expect(task.reload).to be_completed
68
70
  end
69
71
  end
@@ -1,3 +1,14 @@
1
1
  class Task < ActiveRecord::Base
2
2
  has_event :complete
3
+ has_event :archive, skip_scopes: true
4
+
5
+ def complete!
6
+ super
7
+ logger.info("Task #{id} has been completed")
8
+ end
9
+
10
+ def self.complete_all
11
+ super
12
+ logger.info('All tasks have been completed')
13
+ end
3
14
  end
@@ -1,6 +1,5 @@
1
- # Set up gems listed in the Gemfile
2
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
1
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
3
2
 
4
3
  require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5
4
 
6
- $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
5
+ $LOAD_PATH.unshift(File.expand_path('../../../lib', __dir__))
@@ -1,14 +1,16 @@
1
- require File.expand_path('../boot', __FILE__)
1
+ require File.expand_path('boot', __dir__)
2
2
 
3
3
  require 'active_record'
4
4
 
5
5
  Bundler.require(:default, ENV['RAILS_ENV'])
6
6
 
7
7
  # Load application files
8
- Dir["#{File.dirname(__FILE__)}/../app/**/*.rb"].each { |f| require f }
8
+ Dir["#{__dir__}/../app/**/*.rb"].each { |f| require f }
9
9
 
10
10
  # Load the database configuration
11
- config_file = File.expand_path('../database.yml', __FILE__)
12
- config = YAML::load_file(config_file)[ENV['RAILS_ENV']]
11
+ config_file = File.expand_path('database.yml', __dir__)
12
+ config = YAML.load_file(config_file)[ENV['RAILS_ENV']]
13
+
14
+ ActiveRecord::Base.logger = Logger.new(File::NULL)
13
15
 
14
16
  ActiveRecord::Base.establish_connection(config)
@@ -10,7 +10,7 @@
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: 2017_01_03_215307) do
13
+ ActiveRecord::Schema.define(version: 2015_08_13_132804) do
14
14
 
15
15
  create_table "tasks", force: :cascade do |t|
16
16
  t.datetime "completed_at"
@@ -18,10 +18,4 @@ ActiveRecord::Schema.define(version: 2017_01_03_215307) do
18
18
  t.datetime "updated_at", null: false
19
19
  end
20
20
 
21
- create_table "users", force: :cascade do |t|
22
- t.datetime "email_confirmed_at"
23
- t.datetime "created_at", null: false
24
- t.datetime "updated_at", null: false
25
- end
26
-
27
21
  end
Binary file
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'generators/active_record/event/event_generator'
3
+
4
+ RSpec.describe ActiveRecord::Generators::EventGenerator, type: :generator do
5
+ arguments %w[task complete --field-type=date --skip-scopes]
6
+ destination File.expand_path('../../../../tmp', __dir__)
7
+
8
+ before { prepare_destination }
9
+
10
+ it 'generates a migration file' do
11
+ run_generator
12
+
13
+ assert_migration 'db/migrate/add_completed_on_to_tasks' do |migration|
14
+ assert_instance_method :change, migration do |content|
15
+ assert_match 'add_column :tasks, :completed_on, :date', content
16
+ end
17
+ end
18
+ end
19
+
20
+ context 'when the model file exists' do
21
+ before do
22
+ write_file 'app/models/task.rb', <<-RUBY.strip_heredoc
23
+ class Task < ActiveRecord::Base
24
+ end
25
+ RUBY
26
+ end
27
+
28
+ it 'updates the model file' do
29
+ run_generator
30
+
31
+ assert_file 'app/models/task.rb', <<-RUBY.strip_heredoc
32
+ class Task < ActiveRecord::Base
33
+ has_event :complete, field_type: :date, skip_scopes: true
34
+ end
35
+ RUBY
36
+ end
37
+ end
38
+
39
+ context "when the model file doesn't exist" do
40
+ it "doesn't create the model file" do
41
+ run_generator
42
+ assert_no_file 'app/models/task.rb'
43
+ end
44
+ end
45
+ end
@@ -1,15 +1,18 @@
1
1
  ENV['RAILS_ENV'] ||= 'test'
2
2
 
3
- require File.expand_path('../dummy/config/environment.rb', __FILE__)
3
+ require File.expand_path('dummy/config/environment.rb', __dir__)
4
4
 
5
5
  require 'factory_girl'
6
+ require 'generator_spec'
6
7
  require 'timecop'
8
+ require 'zonebie/rspec'
7
9
 
8
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
10
+ Dir["#{__dir__}/support/**/*.rb"].each { |f| require f }
9
11
 
10
12
  RSpec.configure do |config|
11
13
  config.color = true
12
14
  config.order = :random
13
15
 
14
16
  config.include FactoryGirl::Syntax::Methods
17
+ config.include GeneratorHelpers, type: :generator
15
18
  end
@@ -1,4 +1,3 @@
1
1
  FactoryGirl.define do
2
2
  factory :task
3
- factory :user
4
3
  end
@@ -0,0 +1,8 @@
1
+ module GeneratorHelpers
2
+ def write_file(file_name, contents)
3
+ file_path = File.expand_path(file_name, destination_root)
4
+
5
+ FileUtils.mkdir_p(File.dirname(file_path))
6
+ File.write(file_path, contents)
7
+ end
8
+ end
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: 2.2.0
4
+ version: 3.0.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: 2019-03-17 00:00:00.000000000 Z
11
+ date: 2020-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -42,30 +42,72 @@ dependencies:
42
42
  name: appraisal
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2.2'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2.2'
55
55
  - !ruby/object:Gem::Dependency
56
- name: standalone_migrations
56
+ name: factory_girl
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 4.8.1
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 4.8.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: generator_spec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.9'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.50.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.50.0
69
111
  - !ruby/object:Gem::Dependency
70
112
  name: sqlite3
71
113
  requirement: !ruby/object:Gem::Requirement
@@ -81,47 +123,47 @@ dependencies:
81
123
  - !ruby/object:Gem::Version
82
124
  version: 1.3.13
83
125
  - !ruby/object:Gem::Dependency
84
- name: rspec
126
+ name: standalone_migrations
85
127
  requirement: !ruby/object:Gem::Requirement
86
128
  requirements:
87
- - - ">="
129
+ - - "~>"
88
130
  - !ruby/object:Gem::Version
89
- version: '0'
131
+ version: '5.2'
90
132
  type: :development
91
133
  prerelease: false
92
134
  version_requirements: !ruby/object:Gem::Requirement
93
135
  requirements:
94
- - - ">="
136
+ - - "~>"
95
137
  - !ruby/object:Gem::Version
96
- version: '0'
138
+ version: '5.2'
97
139
  - !ruby/object:Gem::Dependency
98
- name: factory_girl
140
+ name: timecop
99
141
  requirement: !ruby/object:Gem::Requirement
100
142
  requirements:
101
- - - ">="
143
+ - - "~>"
102
144
  - !ruby/object:Gem::Version
103
- version: '0'
145
+ version: '0.9'
104
146
  type: :development
105
147
  prerelease: false
106
148
  version_requirements: !ruby/object:Gem::Requirement
107
149
  requirements:
108
- - - ">="
150
+ - - "~>"
109
151
  - !ruby/object:Gem::Version
110
- version: '0'
152
+ version: '0.9'
111
153
  - !ruby/object:Gem::Dependency
112
- name: timecop
154
+ name: zonebie
113
155
  requirement: !ruby/object:Gem::Requirement
114
156
  requirements:
115
- - - ">="
157
+ - - "~>"
116
158
  - !ruby/object:Gem::Version
117
- version: '0'
159
+ version: '0.6'
118
160
  type: :development
119
161
  prerelease: false
120
162
  version_requirements: !ruby/object:Gem::Requirement
121
163
  requirements:
122
- - - ">="
164
+ - - "~>"
123
165
  - !ruby/object:Gem::Version
124
- version: '0'
166
+ version: '0.6'
125
167
  description: An ActiveRecord extension providing convenience methods for timestamp
126
168
  management.
127
169
  email: pienkowb@gmail.com
@@ -129,26 +171,31 @@ executables: []
129
171
  extensions: []
130
172
  extra_rdoc_files: []
131
173
  files:
132
- - MIT-LICENSE
174
+ - LICENSE
133
175
  - README.md
134
176
  - Rakefile
135
177
  - lib/active_record/events.rb
178
+ - lib/active_record/events/macro.rb
136
179
  - lib/active_record/events/naming.rb
180
+ - lib/active_record/events/verbs.rb
137
181
  - lib/active_record/events/version.rb
182
+ - lib/generators/active_record/event/USAGE
183
+ - lib/generators/active_record/event/event_generator.rb
184
+ - spec/active_record/events/macro_spec.rb
138
185
  - spec/active_record/events/naming_spec.rb
139
186
  - spec/active_record/events_spec.rb
140
187
  - spec/dummy/app/models/task.rb
141
- - spec/dummy/app/models/user.rb
142
188
  - spec/dummy/config/boot.rb
143
189
  - spec/dummy/config/database.yml
144
190
  - spec/dummy/config/environment.rb
145
191
  - spec/dummy/db/development.sqlite3
146
192
  - spec/dummy/db/migrate/20150813132804_create_tasks.rb
147
- - spec/dummy/db/migrate/20170103215307_create_users.rb
148
193
  - spec/dummy/db/schema.rb
149
194
  - spec/dummy/db/test.sqlite3
195
+ - spec/generators/active_record/event/event_generator_spec.rb
150
196
  - spec/spec_helper.rb
151
197
  - spec/support/factories.rb
198
+ - spec/support/generator_helpers.rb
152
199
  homepage: https://github.com/pienkowb/active_record-events
153
200
  licenses:
154
201
  - MIT
@@ -161,30 +208,30 @@ required_ruby_version: !ruby/object:Gem::Requirement
161
208
  requirements:
162
209
  - - ">="
163
210
  - !ruby/object:Gem::Version
164
- version: 1.9.3
211
+ version: 2.0.0
165
212
  required_rubygems_version: !ruby/object:Gem::Requirement
166
213
  requirements:
167
214
  - - ">="
168
215
  - !ruby/object:Gem::Version
169
216
  version: '0'
170
217
  requirements: []
171
- rubyforge_project:
172
- rubygems_version: 2.7.8
218
+ rubygems_version: 3.0.3
173
219
  signing_key:
174
220
  specification_version: 4
175
221
  summary: Manage timestamps in ActiveRecord models
176
222
  test_files:
177
223
  - spec/spec_helper.rb
178
- - spec/dummy/app/models/user.rb
179
224
  - spec/dummy/app/models/task.rb
180
225
  - spec/dummy/config/environment.rb
181
226
  - spec/dummy/config/database.yml
182
227
  - spec/dummy/config/boot.rb
183
228
  - spec/dummy/db/schema.rb
184
229
  - spec/dummy/db/test.sqlite3
185
- - spec/dummy/db/migrate/20170103215307_create_users.rb
186
230
  - spec/dummy/db/migrate/20150813132804_create_tasks.rb
187
231
  - spec/dummy/db/development.sqlite3
188
232
  - spec/active_record/events/naming_spec.rb
233
+ - spec/active_record/events/macro_spec.rb
189
234
  - spec/active_record/events_spec.rb
235
+ - spec/support/generator_helpers.rb
190
236
  - spec/support/factories.rb
237
+ - spec/generators/active_record/event/event_generator_spec.rb
@@ -1,3 +0,0 @@
1
- class User < ActiveRecord::Base
2
- has_event :confirm, object: :email
3
- end
@@ -1,9 +0,0 @@
1
- class CreateUsers < ACTIVE_RECORD_MIGRATION_CLASS
2
- def change
3
- create_table :users do |t|
4
- t.datetime :email_confirmed_at
5
-
6
- t.timestamps null: false
7
- end
8
- end
9
- end