An ActiveRecord extension providing convenience methods for timestamp management.
Screencast
Watch screencast (courtesy of Mike Rogers)
Installation
Add the following line to your application's Gemfile:
gem 'active_record-events'
Install the gem with Bundler:
$ bundle install
Or do it manually by running:
$ gem install active_record-events
Usage
Recording a timestamp in order to mark that an event occurred to an object is a common practice when dealing with ActiveRecord models.
A good example of such an approach is how ActiveRecord handles the created_at
and updated_at
fields.
This gem allows you to manage custom timestamp fields in the exact same manner.
Example
Consider a Task
model with a completed_at
field and the following methods:
class Task < ActiveRecord::Base
def completed?
completed_at.present?
end
def not_completed?
!completed?
end
def complete
complete! if not_completed?
end
def complete!
touch(:completed_at)
end
def self.complete_all
touch_all(:completed_at)
end
end
Instead of defining all of these methods by hand, you can use the has_event
macro provided by the gem.
class Task < ActiveRecord::Base
has_event :complete
end
As a result, the methods will be generated automatically.
It's important to note that the completed_at
column has to already exist in the database.
Consider using the generator to create a necessary migration.
Scopes
In addition, the macro defines two scope methods – one for retrieving objects with a recorded timestamp and one for those without it, for example:
scope :not_completed, -> { where(completed_at: nil) }
scope :completed, -> { where.not(completed_at: nil) }
The inclusion of scope methods can be omitted by passing the skip_scopes
flag.
has_event :complete, skip_scopes: true
Multiple events
Using the macro is efficient when more than one field has to be handled that way. In such a case, many lines of code can be replaced with an expressive one-liner.
has_events :complete, :archive
Date fields
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:
has_event :complete, field_type: :date
Custom field name
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.
has_event :complete, field_name: :completion_time
Note that the field_name
option takes precedence over the field_type
option.
Comparison strategy
By default the timestamp's presence will dictate the behavior. However in some cases you may want to check against the current time.
You can do this with the strategy
option, which can be either presence
or time_comparison
:
has_event :complete, strategy: :time_comparison
Example:
task.completed_at = 1.hour.ago
task.completed? # => true
task.completed_at = 1.hour.from_now
task.completed? # => false
Specifying an object
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.
In order to keep method names grammatically correct, you can specify an object using the object
option.
class User < ActiveRecord::Base
has_event :confirm, object: :email
end
This will generate the following methods:
email_not_confirmed?
email_confirmed?
confirm_email
confirm_email!
-
confirm_all_emails
(class method)
As well as these two scopes:
email_confirmed
email_not_confirmed
Using a Rails generator
If you want to quickly add a new event, you can make use of a Rails generator provided by the gem:
$ rails generate active_record:event task complete
It will create a necessary migration and insert a has_event
statement into the model class.
# db/migrate/XXX_add_completed_at_to_tasks.rb
class AddCompletedAtToTasks < ActiveRecord::Migration[6.0]
def change
add_column :tasks, :completed_at, :datetime
end
end
# app/models/task.rb
class Task < ActiveRecord::Base
has_event :complete
end
All of the macro options are supported by the generator and can be passed via the command line. For instance:
$ rails generate active_record:event user confirm --object=email --skip-scopes
For more information, run the generator with the --help
option.
Overriding methods
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.
This applies to instance methods as well as class methods.
In both cases, the super
keyword invokes the original method.
class Task < ActiveRecord::Base
has_event :complete
def complete!
super
logger.info("Task #{id} has been completed")
end
def self.complete_all
super
logger.info('All tasks have been completed')
end
end