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). 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
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
class Stash < ApplicationRecord
  belongs_to :stashable, polymorphic: true
  serialize :hash_data, Hash
end
An example of using Stash as a STI model
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)
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:

def group_params
  params.require(:group).permit(:name, :rounds_used, :use_hi_lo_rule, 
  :club_id,:tees, options: Group.new.options.keys)
end