For years I've been using a serialized column in a model to store semi-constant setting/preferences. I've used various schemes to update, change, add or delete these preference. Updating values is fairly simple, just use a form that defines the key/value settings[some_field] value
. Adding and deleting something is a little more involved and usually result in a re-deploy (since you had to add code to take care of the change!).
I've been playing with a new app that will never get deployed. I just wanted to see how difficult it would be to replicate Point-Of-Sale(POS) system. A POS is just a high-end cash register. I was more or less in charge of a VFW bar and we had a POS, until Covid forced us to close. We had about 8 months left on our contract, so we had to pay about $200 a month for doing nothing.
After a few weeks playing with it, using all the Rails goodies (Turbo Frames/Streams), it was not that difficult to create a rudimentary POS. It tablet friendly in that 95% is button clicks. I then adding features that dealt with taxes, discounts and other stuff.
class Business < ApplicationRecord
# where preferences/settings are stored
class Department < ApplicationRecord
has_many :items
belongs_to :business
# puts like items under it. Thing like Liquor, Food
# that may have different tax-rates
class Item < ApplicationRecord
belongs_to :department
has_many :ticket_items
# inventory, price
class Employee < ApplicationRecord
has_many :tickets
has_many :tills
# who using the pos
class Ticket < ApplicationRecord
belongs_to :employee
has_many :ticket_items
# a new sale
class TicketItem < ApplicationRecord
belongs_to :ticket
belongs_to :item
# what items are on the ticket (quantity and Price/tax
# at time of sale (think Happy Hour)
class Till < ApplicationRecord
belongs_to :employee
# balances sales with payment
Enough of the POS and getting back to my Title/Question and settings/preferences. My current settings are :
{"item_tax"=>"included",
"taxes_used"=>["sales", "county", "federal", "city", "liquor"],
"employee_discount"=>{"percent"=>0.2, "round"=>0.25},
"discounts_used"=>{"time_discount"=>{"percent"=>0.1, "round"=>0.25}, "special_discount"=>{"percent"=>0.2, "round"=>0.25}}}
item_tax: Defines if the item price includes that tax or it's a separate line
taxes_used: Is defined the the Department. It just sums them, but can be separated by a Taxable class that also defines what the tax rates are based on ticket date. Tax rates do change, but very seldom.
employee_discount: Is a switch on ticket that if the customer is an employee, they get 20% off
discounts_used: Defines the different discounts. time-discount: takes care of changing happy hour prices it my
get_current_price
method.
I was just managing the settings in the console when I discovered that the scaffold view had a settings
field. I just changed it to a text-area
.
The cover image is pretty view of my settings form field. I said, what the heck and changed something and submitted. Low in behold, they changed! - but the serialized hash was now just a string - and everything that depended on the hash broke.
I'm sure Rails uses some form of Hash.to_s to populate the field. I can't think where else it would be used. That gets to my Why?
question. Can anyone think of other reasons to use Hash.to_s other than to view it.
Since my settings: as serialized serialize :settings, JSON
as JSON, what is stored in the DB is JSON but returned as a Ruby Hash.
"{\"item_tax\"=>\"included\", \"taxes_used\"=>[\"sales\", \"county\",
\"federal\", \"city\", \"liquor\"],
\"employee_discount\"=>{\"percent\"=>0.2, \"round\"=>0.25},
\"discounts_used\"=>{\"time_discount\"=>{\"percent\"=>0.1, \"round\"=>0.25},
\"special_discount\"=>{\"percent\"=>0.2, \"round\"=>0.25}}}"
If I convert the hash to json (j = b.settings.to_json) I get:
"{\"item_tax\":\"included\",\"taxes_used\":[\"sales\",\"county\",
\"federal\",\"city\",\"liquor\"],
\"employee_discount\":{\"percent\":0.2,\"round\":0.25},
\"discounts_used\":{\"time_discount\":{\"percent\":0.1,\"round\":0.25},
\"special_discount\":{\"percent\":0.2,\"round\":0.25}}}"
See the difference??? Hash.to string basically give Ruby like version of a JSON like string. All that is different is the hash assignment operators. JSON uses :
, Ruby used =>
So I throw together a monkey patch (or could of just used a method) String.json_to_h
# Edit, forgot about array's
# /lib/core_extensins/json_to_h.rb
# convert JSOM to a Hash
module CoreExtensions
module String
def json_to_h
if valid?
JSON.parse(self.gsub('=>', ':'))
end
end
def valid?
default = true
open_hash = self[0] == "{" && self[-1] == "}"
open_arry = self[0] == "[" && self[-1] == "]"
open_match = (self.count('{') + count('['))
close_match = (self.count('}') + count(']'))
return(default && (open_hash || open_arry) && (open_match == close_match))
end
end
end
I added a few sanity checks but it will error in the controller if not valid. I just had the change the scaffold controller a little:
def update
# convert setting to hash and upate latter
settings = business_params['settings'].json_to_h
# settings will be nil if not a valid hash
business_params.delete('settings')
respond_to do |format|
if settings.present?
@business.attributes = business_params
@business.settings = settings
@business.save
...
So I answered my second question Where is String.to_h
Don't think I'd use it except in development, but it was an interesting trip.
Top comments (0)