DEV Community

Steve Alex
Steve Alex

Posted on • Edited on

Ruby/Rails Hash.to_s. Why?, Where is String.to_h?

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
Enter fullscreen mode Exit fullscreen mode

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}}}
Enter fullscreen mode Exit fullscreen mode
  • 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}}}"
Enter fullscreen mode Exit fullscreen mode

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}}}"
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
      ...
Enter fullscreen mode Exit fullscreen mode

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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay