ta_has_event 1.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 +7 -0
- data/LICENSE +20 -0
- data/README.md +214 -0
- data/Rakefile +60 -0
- data/lib/active_record/events/extension.rb +23 -0
- data/lib/active_record/events/macro.rb +36 -0
- data/lib/active_record/events/method_factory.rb +121 -0
- data/lib/active_record/events/naming.rb +85 -0
- data/lib/active_record/events/version.rb +5 -0
- data/lib/active_record/events.rb +13 -0
- data/lib/generators/active_record/event/USAGE +15 -0
- data/lib/generators/active_record/event/event_generator.rb +60 -0
- data/spec/active_record/events/macro_spec.rb +49 -0
- data/spec/active_record/events/method_factory_spec.rb +59 -0
- data/spec/active_record/events/naming_spec.rb +92 -0
- data/spec/active_record/events_spec.rb +74 -0
- data/spec/dummy/app/models/task.rb +14 -0
- data/spec/dummy/config/boot.rb +6 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +16 -0
- data/spec/dummy/db/migrate/20150813132804_create_tasks.rb +9 -0
- data/spec/dummy/db/schema.rb +21 -0
- data/spec/generators/active_record/event/event_generator_spec.rb +45 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/factories/task_factory.rb +3 -0
- data/spec/support/helpers/generator_helpers.rb +8 -0
- data/spec/support/matchers/have_method.rb +5 -0
- metadata +238 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 951ce881bec2caac969973595dd1345226ed2213bd35b66223facb8377229bdb
|
4
|
+
data.tar.gz: 655feb6da1c124ef7fb156a91191580b4777eb34562e15338f01da9175648ec7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab7f97b14543cec599bc5a4118e97fe8dae1b5c03d53c86b3046c1c37f0d2a76a097b555be01881ac0deb862df263b0a73ec77da2abb7a515be3fbae4bcb232a
|
7
|
+
data.tar.gz: b16fad751df98adab0dffd1448b6b7375299c33dee89a6d709b821e29c2f828e4355f82c7a1f7620e7b2fd2e2b88d276572095d83896ed0f293d695105ef86e3
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Bartosz Pieńkowski
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# ActiveRecord::Events [](https://rubygems.org/gems/active_record-events) [](https://travis-ci.org/pienkowb/active_record-events) [](https://coveralls.io/github/pienkowb/active_record-events) [](https://codeclimate.com/github/pienkowb/active_record-events)
|
2
|
+
|
3
|
+
An ActiveRecord extension providing convenience methods for timestamp management.
|
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
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add the following line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'active_record-events'
|
19
|
+
```
|
20
|
+
|
21
|
+
Install the gem with Bundler:
|
22
|
+
|
23
|
+
```
|
24
|
+
$ bundle install
|
25
|
+
```
|
26
|
+
|
27
|
+
Or do it manually by running:
|
28
|
+
|
29
|
+
```
|
30
|
+
$ gem install active_record-events
|
31
|
+
```
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
Recording a timestamp in order to mark that an event occurred to an object is a common practice when dealing with ActiveRecord models.
|
36
|
+
A good example of such an approach is how ActiveRecord handles the `created_at` and `updated_at` fields.
|
37
|
+
This gem allows you to manage custom timestamp fields in the exact same manner.
|
38
|
+
|
39
|
+
### Example
|
40
|
+
|
41
|
+
Consider a `Task` model with a `completed_at` field and the following methods:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class Task < ActiveRecord::Base
|
45
|
+
def completed?
|
46
|
+
completed_at.present?
|
47
|
+
end
|
48
|
+
|
49
|
+
def not_completed?
|
50
|
+
!completed?
|
51
|
+
end
|
52
|
+
|
53
|
+
def complete
|
54
|
+
complete! if not_completed?
|
55
|
+
end
|
56
|
+
|
57
|
+
def complete!
|
58
|
+
touch(:completed_at)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.complete_all
|
62
|
+
touch_all(:completed_at)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Instead of defining all of these methods by hand, you can use the `has_event` macro provided by the gem.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class Task < ActiveRecord::Base
|
71
|
+
has_event :complete
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
As a result, the methods will be generated automatically.
|
76
|
+
|
77
|
+
*It's important to note that the `completed_at` column has to already exist in the database.*
|
78
|
+
*Consider [using the generator](#using-a-rails-generator) to create a necessary migration.*
|
79
|
+
|
80
|
+
### Scopes
|
81
|
+
|
82
|
+
In addition, the macro defines two scope methods – one for retrieving objects with a recorded timestamp and one for those without it, for example:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
scope :not_completed, -> { where(completed_at: nil) }
|
86
|
+
scope :completed, -> { where.not(completed_at: nil) }
|
87
|
+
```
|
88
|
+
|
89
|
+
The inclusion of scope methods can be omitted by passing the `skip_scopes` flag.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
has_event :complete, skip_scopes: true
|
93
|
+
```
|
94
|
+
|
95
|
+
### Multiple events
|
96
|
+
|
97
|
+
Using the macro is efficient when more than one field has to be handled that way.
|
98
|
+
In such a case, many lines of code can be replaced with an expressive one-liner.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
has_events :complete, :archive
|
102
|
+
```
|
103
|
+
|
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
|
+
### Specifying an object
|
123
|
+
|
124
|
+
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.
|
125
|
+
In order to keep method names grammatically correct, you can specify an object using the `object` option.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class User < ActiveRecord::Base
|
129
|
+
has_event :confirm, object: :email
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
This will generate the following methods:
|
134
|
+
|
135
|
+
- `email_not_confirmed?`
|
136
|
+
- `email_confirmed?`
|
137
|
+
- `confirm_email`
|
138
|
+
- `confirm_email!`
|
139
|
+
- `confirm_all_emails` (class method)
|
140
|
+
|
141
|
+
As well as these two scopes:
|
142
|
+
|
143
|
+
- `email_confirmed`
|
144
|
+
- `email_not_confirmed`
|
145
|
+
|
146
|
+
### Using a Rails generator
|
147
|
+
|
148
|
+
If you want to quickly add a new event, you can make use of a Rails generator provided by the gem:
|
149
|
+
|
150
|
+
```
|
151
|
+
$ rails generate active_record:event task complete
|
152
|
+
```
|
153
|
+
|
154
|
+
It will create a necessary migration and insert a `has_event` statement into the model class.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
# db/migrate/XXX_add_completed_at_to_tasks.rb
|
158
|
+
|
159
|
+
class AddCompletedAtToTasks < ActiveRecord::Migration[6.0]
|
160
|
+
def change
|
161
|
+
add_column :tasks, :completed_at, :datetime
|
162
|
+
end
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
# app/models/task.rb
|
168
|
+
|
169
|
+
class Task < ActiveRecord::Base
|
170
|
+
has_event :complete
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
All of the macro options are supported by the generator and can be passed via the command line.
|
175
|
+
For instance:
|
176
|
+
|
177
|
+
```
|
178
|
+
$ rails generate active_record:event user confirm --object=email --skip-scopes
|
179
|
+
```
|
180
|
+
|
181
|
+
For more information, run the generator with the `--help` option.
|
182
|
+
|
183
|
+
### Overriding methods
|
184
|
+
|
185
|
+
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.
|
186
|
+
This applies to instance methods as well as class methods.
|
187
|
+
In both cases, the `super` keyword invokes the original method.
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
class Task < ActiveRecord::Base
|
191
|
+
has_event :complete
|
192
|
+
|
193
|
+
def complete!
|
194
|
+
super
|
195
|
+
logger.info("Task #{id} has been completed")
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.complete_all
|
199
|
+
super
|
200
|
+
logger.info('All tasks have been completed')
|
201
|
+
end
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
## Contributors
|
206
|
+
|
207
|
+
- [Bartosz Pieńkowski](https://github.com/pienkowb)
|
208
|
+
- [Tomasz Skupiński](https://github.com/tskupinski)
|
209
|
+
- [Oskar Janusz](https://github.com/oskaror)
|
210
|
+
- [Mike Rogers](https://github.com/MikeRogers0)
|
211
|
+
|
212
|
+
## See also
|
213
|
+
|
214
|
+
- [ActiveRecord::Enum](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html)
|
data/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'rdoc/task'
|
11
|
+
rescue LoadError
|
12
|
+
require 'rdoc/rdoc'
|
13
|
+
require 'rake/rdoctask'
|
14
|
+
RDoc::Task = Rake::RDocTask
|
15
|
+
end
|
16
|
+
|
17
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'ActiveRecord::Events'
|
20
|
+
rdoc.options << '--line-numbers'
|
21
|
+
rdoc.rdoc_files.include('README.rdoc')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'active_record'
|
26
|
+
require 'yaml'
|
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
|
42
|
+
|
43
|
+
ACTIVE_RECORD_MIGRATION_CLASS =
|
44
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
45
|
+
ActiveRecord::Migration[4.2]
|
46
|
+
else
|
47
|
+
ActiveRecord::Migration
|
48
|
+
end
|
49
|
+
|
50
|
+
load 'active_record/railties/databases.rake'
|
51
|
+
|
52
|
+
Bundler::GemHelper.install_tasks
|
53
|
+
|
54
|
+
require 'rspec/core'
|
55
|
+
require 'rspec/core/rake_task'
|
56
|
+
|
57
|
+
desc 'Run all specs in spec directory (excluding plugin specs)'
|
58
|
+
RSpec::Core::RakeTask.new(spec: 'db:test:prepare')
|
59
|
+
|
60
|
+
task default: :spec
|
@@ -0,0 +1,23 @@
|
|
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
|
+
naming = method_factory.naming
|
15
|
+
attribute naming.field, naming.field_database_type, index: true
|
16
|
+
attribute naming.boolean_attribute, :boolean, index: true, default: -> { false }
|
17
|
+
|
18
|
+
include method_factory.instance_methods
|
19
|
+
extend method_factory.class_methods
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
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
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'active_record/events/naming'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Events
|
5
|
+
class MethodFactory
|
6
|
+
attr_reader :naming
|
7
|
+
|
8
|
+
def initialize(event_name, options)
|
9
|
+
@options = options
|
10
|
+
@naming = Naming.new(event_name, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def instance_methods
|
14
|
+
Module.new.tap do |module_|
|
15
|
+
field_type = naming.field_database_type
|
16
|
+
|
17
|
+
define_predicate_method(module_, naming)
|
18
|
+
define_inverse_predicate_method(module_, naming)
|
19
|
+
|
20
|
+
define_action_method(module_, naming)
|
21
|
+
define_inverse_action_method(module_, naming)
|
22
|
+
define_safe_action_method(module_, naming)
|
23
|
+
define_inverse_safe_action_method(module_, naming)
|
24
|
+
define_boolean_attribute_method(module_, naming)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def class_methods
|
29
|
+
Module.new.tap do |module_|
|
30
|
+
define_collective_action_method(module_, naming)
|
31
|
+
|
32
|
+
unless options[:skip_scopes]
|
33
|
+
define_scope_method(module_, naming)
|
34
|
+
define_inverse_scope_method(module_, naming)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :options
|
42
|
+
|
43
|
+
def define_predicate_method(module_, naming)
|
44
|
+
module_.send(:define_method, naming.predicate) do
|
45
|
+
self[naming.field].present?
|
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
|
+
__send__("#{naming.boolean_attribute}=", true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def define_inverse_action_method(module_, naming)
|
62
|
+
module_.send(:define_method, naming.inverse_action) do
|
63
|
+
__send__("#{naming.boolean_attribute}=", false)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def define_safe_action_method(module_, naming)
|
68
|
+
module_.send(:define_method, naming.safe_action) do
|
69
|
+
if __send__(naming.inverse_predicate)
|
70
|
+
__send__(naming.action)
|
71
|
+
save!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def define_inverse_safe_action_method(module_, naming)
|
77
|
+
module_.send(:define_method, naming.inverse_safe_action) do
|
78
|
+
if __send__(naming.inverse_predicate)
|
79
|
+
__send__(naming.inverse_action)
|
80
|
+
save!
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# naming.boolean_attribute=
|
86
|
+
# is_object_completed=
|
87
|
+
def define_boolean_attribute_method(module_, naming)
|
88
|
+
module_.send(:define_method, "#{naming.boolean_attribute}=") do |val|
|
89
|
+
if val == true && self[naming.field].blank?
|
90
|
+
self[naming.field] = current_time_from_proper_timezone
|
91
|
+
elsif val == false && self[naming.field].present?
|
92
|
+
self[naming.field] = nil
|
93
|
+
end
|
94
|
+
super(val)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def define_collective_action_method(module_, naming)
|
99
|
+
module_.send(:define_method, naming.collective_action) do
|
100
|
+
if respond_to?(:touch_all)
|
101
|
+
touch_all(naming.field)
|
102
|
+
else
|
103
|
+
update_all(naming.field => Time.current)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def define_scope_method(module_, naming)
|
109
|
+
module_.send(:define_method, naming.scope) do
|
110
|
+
where(arel_table[naming.field].not_eq(nil))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def define_inverse_scope_method(module_, naming)
|
115
|
+
module_.send(:define_method, naming.inverse_scope) do
|
116
|
+
where(arel_table[naming.field].eq(nil))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'verbs'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Events
|
5
|
+
class Naming
|
6
|
+
def initialize(infinitive, options = {})
|
7
|
+
@infinitive = infinitive
|
8
|
+
@object = options[:object].presence
|
9
|
+
@field_name = options[:field_name].to_s
|
10
|
+
@field_type = options[:field_type].try(:to_sym)
|
11
|
+
end
|
12
|
+
|
13
|
+
def field
|
14
|
+
return field_name if field_name.present?
|
15
|
+
|
16
|
+
suffix = field_type == :date ? 'on' : 'at'
|
17
|
+
|
18
|
+
concatenate(object, past_participle, suffix)
|
19
|
+
end
|
20
|
+
|
21
|
+
def field_database_type
|
22
|
+
field_type || :datetime
|
23
|
+
end
|
24
|
+
|
25
|
+
def boolean_attribute
|
26
|
+
concatenate('is', object, past_participle)
|
27
|
+
end
|
28
|
+
|
29
|
+
def predicate
|
30
|
+
concatenate(object, past_participle) + '?'
|
31
|
+
end
|
32
|
+
|
33
|
+
def inverse_predicate
|
34
|
+
concatenate(object, 'not', past_participle) + '?'
|
35
|
+
end
|
36
|
+
|
37
|
+
def action
|
38
|
+
concatenate(infinitive, object)
|
39
|
+
end
|
40
|
+
|
41
|
+
def inverse_action
|
42
|
+
concatenate('not', infinitive, object)
|
43
|
+
end
|
44
|
+
|
45
|
+
def safe_action
|
46
|
+
concatenate(infinitive, object) + '!'
|
47
|
+
end
|
48
|
+
|
49
|
+
def inverse_safe_action
|
50
|
+
concatenate('not', infinitive, object) + '!'
|
51
|
+
end
|
52
|
+
|
53
|
+
def collective_action
|
54
|
+
concatenate(infinitive, 'all', plural_object)
|
55
|
+
end
|
56
|
+
|
57
|
+
def scope
|
58
|
+
concatenate(object, past_participle)
|
59
|
+
end
|
60
|
+
|
61
|
+
def inverse_scope
|
62
|
+
concatenate(object, 'not', past_participle)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_reader :infinitive
|
68
|
+
attr_reader :object
|
69
|
+
attr_reader :field_name
|
70
|
+
attr_reader :field_type
|
71
|
+
|
72
|
+
def concatenate(*parts)
|
73
|
+
parts.compact.join('_')
|
74
|
+
end
|
75
|
+
|
76
|
+
def past_participle
|
77
|
+
infinitive.verb.conjugate(tense: :past, aspect: :perfective)
|
78
|
+
end
|
79
|
+
|
80
|
+
def plural_object
|
81
|
+
object.to_s.pluralize if object.present?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
require 'active_record/events/version'
|
4
|
+
|
5
|
+
require 'active_record/events/naming'
|
6
|
+
require 'active_record/events/method_factory'
|
7
|
+
require 'active_record/events/extension'
|
8
|
+
|
9
|
+
require 'active_record/events/macro'
|
10
|
+
|
11
|
+
ActiveSupport.on_load(:active_record) do
|
12
|
+
extend ActiveRecord::Events::Extension
|
13
|
+
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
|