Everyone knows engineers can’t write! I
is
an
engineeer!
But...
I Wish I Could Write!
Back to articles
I hate to start off by talking about a kludge from my "old school" programing background, but it's something I've used as a prototyping tool that sometimes found it's way into production. I have to start with a short story about the 15 years or so I had to deal with [4D (4th Dimension, or Silver Surfer)](https://en.wikipedia.org/wiki/4th_Dimension_(software)). A powerful Macintosh tool from the 80's, but a horrible language and implementation. I could spew all kinds of reasons I hated 4D but I'll limit it to why I came up with the "Stash" technique. 4D was a compiled language. Everything was self contained in a MacIntosh resource. There was no version control, just the developers version. I didn't write in 4D, but I took over a web presence by using a plug-in Active4D. Active4D was more or less a PHP like language that interfaced with the 4D structure. My developer controlled the structure and if he was working on a new feature, I couldn't do anything for sometimes months until a new compiled version was released. I was under pressure to release a web based application and my 4D developer was under pressure to release new GUI applications - not a good mix. I forced a new release with a **Documents** table that in Rails terms was a polymorphic table. I had generic fields for IDs, keys, and most of 4Ds basic data type. I used that table to link new data fields to other records until we could get them added to the structure. Unfortunately some of that stuff is probably still out there, but just like Rails polymorphic tables, it worked and there is sometimes a need to store persistent data somewhere. I've used the concept of a Stash in several projects, maybe because I'm "old school", maybe because I was still prototyping and didn't know what data I needed, or just because! The Stash model is a combination of a Single Table Inheritance, Polymorphic stashable relations and YAML serialization. Talk about a kludge! ##### The Table ```ruby create_table "stashes", force: :cascade do |t| t.string "stashable_type" t.integer "stashable_id" t.string "type" t.date "date" t.text "hash_data" t.text "text_data" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["stashable_type", "stashable_id"], name: "index_stashes_on_stashable_type_and_stashable_id", using: :btree t.index ["type"], name: "index_stashes_on_type", using: :btree end ``` ##### The Model ```ruby class Stash < ApplicationRecord belongs_to :stashable, polymorphic: true serialize :hash_data, Hash end ``` ##### An example of using Stash as a STI model ```ruby class UpcomingEvents < Stash def events self.hash_data end def events=(event) self.hash_data = event end end ``` This use of Stash stores or retrieve upcoming iCalendar events for some entity. The entity defines and controls updating or refreshing of the events. ##### An example of using Stash to store preferences/options for model (has_one :group_options) ```ruby class GroupOptions < Stash def options=(params) self.data = self.normalize_options(self.options.merge(params)) end def option_types types = { par_in: :string, par_out: :string, welcome: :text, alert: :text, notice: :text, tee_time: :string, play_days: :string, dues: :integer, skins_dues: :integer, par3_dues: :integer, other_dues: :integer, truncate_quota: :boolean, pay: :string, limit_new_player: :boolean, limit_rounds: :integer, limit_points: :integer }.with_indifferent_access end def options attrib = { par_in:"443455435", par_out:'453445344', welcome:"Welcome to #{self.name}", alert:'', notice:'', tee_time:'9:30am', play_days:'Mon, Wed, Fri', dues:6, skins_dues: 0, par3_dues: 0, other_dues: 0, truncate_quota:true, pay:'sides', limit_new_player:false, limit_rounds:1, limit_points:2 }.with_indifferent_access # merge with current data in case new attribute add normalize_options(attrib.merge(self.data)) end def normalize_options(params) types = self.option_types params.each_pair do |k,v| if types[k] == :integer params[k] = v.to_i elsif types[k] == :boolean params[k] = (v.to_s =~ /^[Tt1].*$/i) == 0 end end # renames or deletes if required during prototype x = params.delete(:skin_dues) params[:skins_dues] = x unless x.nil? #rename # params.delete(:not_used_anymore) #delete params end ``` I've used this in prototyping, where everything we need may not be totally defined. When there is agreement, this can be moved to or a table. Think of it as a temporary table! I've also used the methods is a model that had a serialized field. You do have to require the hash params, for example: ``` ruby def group_params params.require(:group).permit(:name, :rounds_used, :use_hi_lo_rule, :club_id,:tees, options: Group.new.options.keys) end ``` .md
Stash Kludge
February 21, 2017 02:10