hackatime/app/models/goal.rb
Mahad Kalam e3456be187
goaaaal! (#985)
* Add goals

* Fix up some migrations

* Formatting

* Simplify migration

* Update test/controllers/settings_goals_controller_test.rb

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update test/controllers/settings_goals_controller_test.rb

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Fix svelte-check issues, make CI less janky on dev

* svelte-check/fix tests

* Fix N+1s

* Formatting!

* More tests, fix anonymization and N+1

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-02-19 18:47:01 +00:00

86 lines
2.3 KiB
Ruby

class Goal < ApplicationRecord
PERIODS = %w[day week month].freeze
PRESET_TARGET_SECONDS = [
30.minutes.to_i,
1.hour.to_i,
2.hours.to_i,
4.hours.to_i
].freeze
MAX_TARGET_SECONDS_BY_PERIOD = {
"day" => 24.hours.to_i,
"week" => 7.days.to_i,
"month" => 31.days.to_i
}.freeze
MAX_GOALS = 5
belongs_to :user
before_validation :normalize_fields
validates :period, inclusion: { in: PERIODS }
validates :target_seconds, numericality: { only_integer: true, greater_than: 0 }
validate :languages_must_be_string_array
validate :projects_must_be_string_array
validate :target_must_fit_within_period
validate :max_goals_per_user
validate :no_duplicate_goal_for_user
def as_programming_goal_payload
{
id: id.to_s,
period: period,
target_seconds: target_seconds,
languages: languages,
projects: projects
}
end
private
def normalize_fields
self.period = period.to_s
self.languages = Array(languages).map(&:to_s).compact_blank.uniq.sort
self.projects = Array(projects).map(&:to_s).compact_blank.uniq.sort
end
def languages_must_be_string_array
return if languages.is_a?(Array) && languages.all? { |language| language.is_a?(String) }
errors.add(:languages, "must be an array of strings")
end
def projects_must_be_string_array
return if projects.is_a?(Array) && projects.all? { |project| project.is_a?(String) }
errors.add(:projects, "must be an array of strings")
end
def target_must_fit_within_period
max_seconds = MAX_TARGET_SECONDS_BY_PERIOD[period]
return if max_seconds.blank?
return if target_seconds.to_i <= max_seconds
errors.add(:target_seconds, "cannot exceed #{max_seconds / 3600} hours for a #{period} goal")
end
def max_goals_per_user
return if user.blank?
current_goal_count = user.goals.where.not(id: id).count
return if current_goal_count < MAX_GOALS
errors.add(:base, "cannot have more than #{MAX_GOALS} goals")
end
def no_duplicate_goal_for_user
return if user.blank?
duplicate_exists = user.goals
.where.not(id: id)
.exists?(period: period, target_seconds: target_seconds, languages: languages, projects: projects)
return unless duplicate_exists
errors.add(:base, "duplicate goal")
end
end