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.