active_record-events 2.2.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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