Steve Alex
Posted on August 20, 2020
I'll admit it I'm not a 'good' coder. I was never employed as a programmer, but I did managed development projects as functional analysts
. I often got upset if the programmers didn't understand the specs (usually with good reason!) so I'd try to clarify the specs with steve code
, which was usually a hack!
I've used many hacks over the years for various reasons (e.g., I was not very familiar with a new language, I was not sure how I would implement what I was trying to do, etc). One of my favorite hacks is to use a serialized hash to define preferences/setting. The goal was to use the hack in development and then add the preferences to columns before production. I often did not accomplish that goal and just used the hash.
Preferences are usually data that seldom get changed, does not need to be queried etc. They may be used as an argument to a calculation, define how something is displayed, etc. I'll use a model Group
to explain how this is used. The Group
in my example of a Golf group that has many Players
and Games
. The purpose of the application is to compute a quota for each player (similar to handicaps). I have a number of Clubs/Groups that use the application to manage their information. The group has options that controll how these quotas are computed. They can use a different number of Rounds
and other options in the computation. They may throw out the high and low score in the computation. They may limit the score used in the quota computation for a new player.
My original scheme was to use three hashes in the process. One defined the default keys and values. One defined the casting (string, integer, boolean, etc) of each key/value and one, which is the serialized hash, merges and casts the input params. The default hash (scaled down version) may look like:
def default_options
options = {
limit_new_player:true,
limit_rounds:3,
limit_points:2,
sanitize_first_round:false,
rounds_used:10,
truncate_quota:true,
use_hi_lo_rule:false,
}.with_indifferent_access
end
The new scheme is to use the attributes API to replace the casting hash. Attributes are added for each key. A scaled down model (relations not present)
class Group < ApplicationRecord
# relations include Club, Players, Game, Round through Game and Users
after_initialize :set_attributes
serialize :preferences, Hash
attribute :limit_new_player, :boolean
attribute :limit_rounds, :integer
attribute :limit_points, :integer
attribute :sanitize_first_round, :boolean
attribute :truncate_quota, :boolean
attribute :rounds_used, :integer
attribute :use_hi_lo_rule, :boolean
def set_attributes
set_default_options if self.preferences.blank?
self.default_options.each do |k,v|
#TODO use self.has_attribute?(k) to raise exception if an attribute is not defined, but testing should catch it
self.send("#{k.to_s}=", self.preferences[k])
end
end
def set_preferences
self.default_options.each do |k,v|
self.preferences[k] = self.send(k)
end
end
def set_default_options
# set preferences to the default option if preferences blank (a new record)
self.preferences = self.default_options.to_h
end
def default_options
options = {
limit_new_player:true,
limit_rounds:3,
limit_points:2,
sanitize_first_round:false,
rounds_used:10,
truncate_quota:true,
use_hi_lo_rule:false,
}.with_indifferent_access
end
def update_group(params)
#called from the update method in the Groups controller, all attributes are in the group_params
self.assign_attributes(params)
# updates record without saving, just sets attributes
self.set_preferences
# now use the set attributes to update to serialized preferences and save
self.save
end
end
This may be still be a hack, but a lot cleaner one than my original scheme. The process if fairly simple
- On initialize the serialized preferences are moved to attributes
- Preferences are just like model attributes and used in computations/views (Current.group.rounds_used)
- Forms don't have to use xx_tags helpers
- You don't have to use Permit on hash for strong params
- Updating preferences acts like real attributes and the attributes are just moved to the serialized hash
I currently have 27 attributes in the current system. Beside the ones used in this post there are attributes that controller the scoring of a game, which team/player(s) won. Golf is a game of skill and games are really mini-tournaments where you pay and entry fee and you may get your money back (or more) if you win.
Posted on August 20, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.