Skip to content

Instantly share code, notes, and snippets.

@ejhayes
Created September 10, 2014 08:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ejhayes/059327748321f04b7f1a to your computer and use it in GitHub Desktop.
Save ejhayes/059327748321f04b7f1a to your computer and use it in GitHub Desktop.
Goals are a high level concept--having a user set a goal should be validated in a way that keeps the model closed for modification, allows maximum flexibility in expressing goal validation per user, and keeps the data in the database rather than mixed throughout the application logic.
# - simplified
module Goals
class WeightLossGoal < Goal
db_identifier :weight_loss_goal
validates_goal_with do |user, amount_weight|
# user is the user we are validating against
if amount_weight >= user.weight
raise InvalidGoalError.new('cannot lose more than you weigh')
end
end
end
end
# - overarching module controlling this
module Goals
def self.load_goals
Goal.registered_goals do |registered_goal|
# make sure it is loaded into the database if it does not exist
# then make sure goals not defined are set to disabled
# to keep this fast, the ID should have an index and do a NOT IN
# query to disable undefined goal types
# NOTE: as conditions change, it is possible for invalid data to
# be in the database...you just cannot update it with invalid data
end
end
end
module Goals
class Goal
@@registered_goals = []
# on inherited, make sure the class is registered
def self.registered_goals
@@registered_goals
end
# declarative stuff here
end
class AmmountGoal < Goal
end
end
module Goals
class WeightLossGoal < AmmountGoal
db_identifier :weight_loss_goal
enabled # disabled would also work
name 'Lose weight'
description 'Lose a certain amount of weight'
validates_goal_with do |user, amount_weight|
# user is the user we are validating against
if amount_weight >= user.weight
raise InvalidGoalError.new('cannot lose more than you weigh')
end
end
end
end
module Goals
class WalkMoreGoal < AmmountGoal
db_identifier :walk_more_goal
enabled
name 'Walk more'
description 'A goal to walk more'
task 'park at the far end of the parking lot'
validates_goal_with do |user, amount_steps|
if amount_steps > 120000
raise InvalidGoalError.new('Too many steps')
end
end
end
end
# - in initializers file
#Goal.load_goals # or Goal.load_goals do |config| end
# any goals that are in the db but not defined in here
# should be disabled in the database so that they cannot
# be used. this could also be pacakged as a separate gem
# that also includes a rails engine with necessary migrations
# for the database...
# the gem could also include an initializer with it
# - migrations to be done
# create goal tables
# id, goal_identifier, enabled
# index on goal_identifier
# - activerecord validator
# in Rails::Engine do: config.autoload_paths << File.expand_path("../app/validators", __FILE__)
# app/validators/goal_validator.rb
class GoalValidator < ActiveModel::Validator
def validate(record)
begin
Goal.find(record.send(options[:goal_identifier])).validate(record.send(options[:user]), record.send(options[:amount]))
rescue => error
record.errors[:base] << error
end
end
end
# - activerecord validator (if goal not defined, no need to do any special processing)
# in Rails::Engine do: config.autoload_paths << File.expand_path("../app/validators", __FILE__)
# app/validators/goal_validator.rb
class GoalValidator < ActiveModel::Validator
def validate(record)
goal = Goal.find(record.send(options[:goal_identifier]))
return if goal.nil?
begin
goal.validate(record.send(options[:user]), record.send(options[:amount]))
rescue => error
record.errors[:base] << error
end
end
end
# - in models, user_goals.rb
class UserGoal < ActiveRecord::Base
validates_with GoalValidator, goal_identifier: :goal_identifier, user: :user, amount: :amount
end
# - use case
User.goals << UserGoal.new('WeightLossGoal', 12) # will run the custom validator
User.goals << UserGoal.new('StepsGoal', 122) # no custom validator will run...if we add one later it will automatically be run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment