I am creating small Time Tracking app (similar to Toggle or Ronin).
I am struggling with a validation of an entity (
TimeEntry) which behavior depends on another associated entity (
Activity). To be more specific,
TimeEntry‘s validations depend on
Activity properties’ values. For example, the user has to fill the
description property of a
TimeEntry only if associated
Activity entity has
description_required property set to
You can see how I dealt with the problem in the attached code. There are a lot of custom predicates, and each one of them accesses the
ActivityReporsitory instance to read the value of certain
Activity property (so that it can be used inside a rule below). I do not like this solution: first, it’s not DRY. Second, it calls the database 5 times while it could call it only once.
The question is: Is there a better way to write validations that depend on an associated entity? How to make this code more DRY?
I know that with
dry-validations I could for example fetch
activity_id from the input params before validation, load the
Activity entity from the database, and then inject
activity object as an external dependency (like this:
scheme.with(activity: activity).call(input)). This way I wouldn’t have to access any repository inside the schema.
But First of all, I cannot find similar feature (injecting external dependencies) in Hanami::Validations. Second of all, it does not look like a good practice to “extract” certain params from the input before validation. After all, the extracted parameter would also have to be validated somewhere.
Here’s the code:
predicate :project_required? do |activity_id|
predicate :project_not_required? do |activity_id|
predicate :all_day_entry? do |activity_id|
predicate :hourly_entry? do |activity_id|
predicate :description_required? do |activity_id|
rule(project_presence: [:activity_id, :project_id]) do |activity_id, project_id|
(activity_id.project_required? > project_id.filled?) &
(activity_id.project_not_required? > project_id.none?)
rule(hours_presence: [:activity_id, :hours]) do |activity_id, hours|
(activity_id.all_day_entry? > hours.none?) &
(activity_id.hourly_entry? > hours.filled?)
rule(description_presence: [:activity_id, :description]) do |activity_id, description|
activity_id.description_required? > description.filled?
attr_accessor :project_id, :hours, :description
class Activity # 'Work', 'R&D', 'Paid Leave', 'Unpaid Leave'
attr_accessor :name, :project_required, :description_required, :is_all_day