0

I have a hash like this:

config = {
  :assets => {
    :path => '/assets',
    :aliases => {
      '/assets/' => '/assets/index.html',
      '/assets/index.htm' => '/assets/index.html'
    }
  },
  :api => {
    :path => '/auth/api',
    :aliases => {
      '/auth/api/me' => '/auth/api/whoami'
    }
  }
}

Is it possible to remove duplicates and have config[...][:aliases] assigned in terms of config[...][:path] like "#{dict_im_in()[:path]}/index.html"?

sawa
  • 165,429
  • 45
  • 277
  • 381
Jan Wrobel
  • 6,969
  • 3
  • 37
  • 53
  • you can handle this in the application. Store alias as `/index.html` and when you read the config, combine it with value of `:path`. – Sergio Tulentsev Jan 08 '13 at 12:33
  • You can, but not inline (to my knowledge). You'll have to add them after loading up. Do what Sergio suggests, but instead of combining it every time, at application startup go through each key-val pair in `config` and update the `aliases` in it by appending the `:path` where appropriate. – Chris Pfohl Jan 08 '13 at 12:42
  • Your example shows no duplication. Or am I missing something? – tokland Jan 08 '13 at 14:15
  • 1
    No. But you don't need to update data, use variables with subexpressions. – tokland Jan 08 '13 at 14:18
  • @tokland paths prefixes (`/assets`, `/auth/api`) are duplicated, which for a large config will be error prone when updates are needed. – Jan Wrobel Jan 08 '13 at 14:59

2 Answers2

1

What you are trying to do smells of premature optimization. I'd recommend concentrating on using symbols as much as possible, both as keys and values, in your hash. Symbols are very memory efficient and fast to lookup because Ruby will only create one memory slot for a given symbol, effectively doing what you're trying to do.

Your example structure could be rewritten using symbols:

config = {
  :assets => {
    :path => :'/assets',
    :aliases => {
      :'/assets/' => :'/assets/index.html',
      :'/assets/index.htm' => :'/assets/index.html'
    }
  },
  :api => {
    :path => :'/auth/api',
    :aliases => {
      :'/auth/api/me' => :'/auth/api/whoami'
    }
  }
}

:'/assets/index.html', no matter where it's seen, would always point to the same symbol, unlike a string.

Any time you need to access a value in something expecting a string, do a to_s to it. When you add a value, do a to_sym to it.

Behind the scenes, you'll be using memory more efficiently in your Hash, and using what should be the fastest value lookup available in Ruby.

Now, if you're using a hash structure to configure your application, and it's being stored on disk, you can get some space savings using YAML, which supports aliases and anchors inside its data file. They won't make pointers inside the hash after parsing into a Ruby object, but they can reduce the size of the file.


EDIT:

If you need to dynamically define a configuration on the fly, then create variables for all the sub-parts of the various strings, then add a method that generates it. Here's the basics for dynamically creating such a thing:

require 'awesome_print'

def make_config(
  assets = '/assets',
  index_html = 'index.html',
  auth_api = '/auth/api'
)
  assets_index = File.join(assets, index_html)
  {
    :assets => {
      :path => assets,
      :aliases => {
        assets + '/' => assets_index,
        assets_index[0..-2] => assets_index
      }
    },
    :api => {
      :path => auth_api,
      :aliases => {
        File.join(auth_api, 'me') => File.join(auth_api, 'whoami')
      }
    }
  }
end

ap make_config()
ap make_config(
  '/new_assets',
  ['new','path','to','index.html'],
  '/auth/new_api'
)

With what would be generated:

{
    :assets => {
          :path  => "/assets",
        :aliases => {
                    "/assets/"  => "/assets/index.html",
            "/assets/index.htm" => "/assets/index.html"
        }
    },
      :api => {
          :path  => "/auth/api",
        :aliases => {
            "/auth/api/me" => "/auth/api/whoami"
        }
    }
}

and:

{
    :assets => {
          :path  => "/new_assets",
        :aliases => {
                                "/new_assets/"  => "/new_assets/new/path/to/index.html",
            "/new_assets/new/path/to/index.htm" => "/new_assets/new/path/to/index.html"
        }
    },
      :api => {
          :path  => "/auth/new_api",
        :aliases => {
            "/auth/new_api/me" => "/auth/new_api/whoami"
        }
    }
}

Edit:

An alternate way of duplicating value pointers, is to create lambdas or procs that return the information you want. I think of them like they're function pointers in C or Perl, allowing us to retain the state of a variable that was in scope, or to manipulate the passed-in values. This isn't a perfect solution, because of variable scoping that occurs, but it's another tool for the box:

lambda1 = lambda { 'bar' }
proc2   = Proc.new { |a,b| a + b }
proc3   = Proc.new { proc2.call(1,2) }

foo = {
  :key1 => lambda1,
  :key2 => proc2,
  :key3 => proc2,
  :key4 => proc3
}

foo[:key1].object_id # => 70222460767180
foo[:key2].object_id # => 70222460365960
foo[:key3].object_id # => 70222460365960
foo[:key4].object_id # => 70222460149600

foo[:key1].call          # => "bar"
foo[:key2].call(1, 2)    # => 3
foo[:key3].call(%w[a b]) # => "ab"
foo[:key4].call          # => 3

See "When to use lambda, when to use Proc.new?" for more information on lambdas and procs.

Community
  • 1
  • 1
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • Actually, I don't care about optimization at all, only about duplicated configuration. If I need to change '/assets/' to '/static/', I need to do it in multiple places. This is of course easy with a small config from the example, but in real life, with more advanced configuration, such duplication tends to be a source of errors. – Jan Wrobel Jan 08 '13 at 14:55
  • I guess the answer to my question is that a value in a hash can not refer to some other values from the same hash. Your answers gives one way to workaround this, thus I'm accepting it. – Jan Wrobel Jan 09 '13 at 16:45
  • I added another way that might seed ideas of how to get where you want to go. – the Tin Man Jan 09 '13 at 18:17
-1

This is an interesting question ;).

I think you could "reopen" the hash and then just use the variable name.

For example:

config = {
  :assets => {
    :path => '/assets',
    :aliases => {
      '/assets/' => '/assets/index.html',
      '/assets/index.htm' => '/assets/index.html'
    }
  }
}
# Then "re-open" the Hash
config[:api] = {
  :path => '/auth/api',
  :aliases => {
    '/auth/api/me' => config[:assets][:aliases]['/assets/index.htm']
  }
}

You could also write a helper method to extract a given alias from that hash. But I'm not sure if it's worth it.

sawa
  • 165,429
  • 45
  • 277
  • 381
Aldo 'xoen' Giambelluca
  • 12,075
  • 7
  • 33
  • 39