Search code examples
rubystore

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?


Solution

  • 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