Ben Etherington Ben Etherington - 1 month ago 6
Ruby Question

Is there a good way to pass "array addresses" to a method?

How can I refactor the following? I have some values stored in my YAML file as nested arrays, but I want to pull all my transactions into two get and set methods. This works, but is obviously limited and bulky. It feels wrong.

module Persistance
@store = YAML::Store.new('store.yml')

def self.get_transaction(key)
@store.transaction { @store[key] }
end

def self.get_nested_transaction(key, sub)
@store.transaction { @store[key][sub] }
end
end


Bonus credit: I also have an additional method for incrementing values in my YAML file. Is there a further way to refactor this code? Does it make sense to just pass blocks to a single database accessing method?

Answer

Hey I remember thinking about this when I was practicing PStore a little while ago. I didn't figure out a working approach then but I managed to get one now. By the way, yaml/store is pretty cool and you can take credit for introducing me to it.

Anyway, on with the code. Basically here's a couple important concepts:

  • The @store is similar to a hash in that you can use [] and []= but it's not actually a hash, it's a YAML::Store.
  • Ruby 2.3 has a method Hash#dig which is kind of the missing puzzle piece here. You provide a list of keys and it treats each as successive keys. You can use this for both get and set, as my code shows
  • If @store were a true hash that would be the end of it but's not, so for this answer I added a YAML::Store#dig method which has the same usage as the original.

require 'yaml/store'

class YAML::Store
  def dig(*keys)
    first_val = self[keys.shift]
    if keys.empty?
      first_val
    else
      keys.reduce(first_val) do |result, key|
        first_val[key]
      end
    end
  end
end

class YamlStore
  attr_reader :store
  def initialize filename
    @store = YAML::Store.new filename
  end
  def get *keys
    @store.transaction do
      @store.dig *keys
    end
  end
  def set *keys, val
    @store.transaction do
      final_key = keys.pop
      hash_to_set = keys.empty? ? @store : @store.dig(*keys)
      hash_to_set.send :[]=, final_key, val
    end
  end
end

filename = 'store.yml'
db = YamlStore.new filename

db.set :a, {}
puts db.get :a
# => {}

db.set :a, :b, 1
puts db.get :a, :b
# => 1