Created
September 10, 2014 08:05
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# - 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