4

I know I can use class as Hash key, but is it a good practice? Is there any drawbacks in terms of performance or testing?

{
  SomeClassA: 'hash value',
  AnotherClass: 'hash value'
}
user1883793
  • 4,011
  • 11
  • 36
  • 65

4 Answers4

9
{
  SomeClassA: 'hash value',
  AnotherClass: 'hash value'
}

Is actually equivalent to:

{
  :SomeClassA => 'hash value',
  :AnotherClass => 'hash value'
}

The keys are symbols. In the "new" literal hash syntax keys are just treated as literals which are cast into symbols (provided they are valid syntax).

To use constants, ranges or any other type of object you can dream up as keys you need to use hashrockets:

{
  SomeClassA => 'hash value',
  AnotherClass => 'hash value'
}

Is it a good practice?

Its a technique that could be used in few limited cases. For example to replace a series of if statements.

def foo(bar)
  if bar.is_a? SomeClassA
    'hash value'
  end
  if bar.is_a? AnotherClass
    'another value'
  end
end

def foo(bar)
  {
    SomeClassA => 'hash value',
    AnotherClass => 'another value'
  }[bar]
end

But I would rather use a case statement there anyways as its clearer in intent and more flexible.

Are there any drawbacks in terms of performance or testing?

Each hash you create would have keys that point to the exact same objects in memory just like if you where using symbols.

One big gotcha is that Rails monkey-patches const_missing to autoload files - when you reference a class name rails will load the file from the file system into memory. This is why you declare associations with:

class Foo < ApplicationRecord
  belongs_to :bar, class_name: "Baz"
end

It lets Rails instead lazy load Baz when needed. You would do the same with the example above by:

def foo(bar)
  {
    'SomeClassA' => 'hash value',
    'AnotherClass' => 'another value'
  }[bar.name]
end
max
  • 96,212
  • 14
  • 104
  • 165
  • Thanks, this is what Im looking for, but how does bar.name.to_sym addresses the autoload issue here? can you give an example? What happens if just pass foo(SomeClassA) is seems fine in most case? – user1883793 Sep 11 '17 at 03:17
  • "...coerces whatever you put in the key.." is perhaps a bit strong. 'Yes' for strings, constants and names of local variables and methods (without leading colon), but as far as I know, 'no' for anything else (integers, floats, symbols, names of instance, class and global variables, arrays, hashes, and so on). You might wish to make that more specific. Aside from that niggle, excellent answer. – Cary Swoveland Sep 11 '17 at 04:08
  • Valid point @CarySwoveland. It really just coerces strings beginning with any letter into symbols as anything else will cause a syntax error, even for example `{ 0_foo: 2 }` – max Sep 11 '17 at 06:01
  • @user1883793 in the example with `bar.name.to_sym` the constants are not referenced - we just use symbols as stand ins. Which means that they are not autoloaded. I edited the example to use strings instead which should make it more straight forward. Usually referencing a single class hardly has a noticeable impact but it is one thing to be aware of. – max Sep 11 '17 at 06:07
  • 1
    That's because `:0_foo` is invalid. You have to use `:'0_foo'` or `{ '0_foo': 2 }` instead. – Stefan Sep 11 '17 at 06:08
  • Not quite @Stefan. If you look at `{ 0_foo: 2 }` the error message is `unexpected tIDENTIFIER, expecting =>` whereas `:0_foo` gives `unexpected tINTEGER, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END`. That means that the parser has not identified that it is a literal hash and treats `0_foo:` as if it where an identifier. – max Sep 11 '17 at 06:13
  • 1
    That's right, the syntax check is performed when parsing the hash. But the same rules _should_ apply. (IIRC, there was a Ruby version which couldn't parse quoted symbol keys) – Stefan Sep 11 '17 at 06:17
4

That hash is using symbols, not classes, as keys, but you can use a Class by doing

hash = { SomeClassA => "some value" }

I can't think of why it would be worse than using any other object because

Classes in Ruby are first-class objects---each is an instance of class Class

So

{ SomeClassA => "some value" }

is functionally equivalent to

{ "Some String" => "some value" }
Simple Lime
  • 10,790
  • 2
  • 17
  • 32
2

What you have there are not class keys but symbols. Try

class A
end

class B
end

hash = {A: "Some", B: "Code"}
hash.keys[0].class
=> Symbol

But still A.class => Class

In case of symbol performance this POST is great

Also you can check ruby documentation about user-defined class as hash keys

Emmanuel Mtali
  • 4,383
  • 3
  • 27
  • 53
  • +1 but declaration of classes A and B in your answer are irrelevant and rather confusing as to why you did it, don't you think? – vee Sep 11 '17 at 00:09
0

While you can do it, I can't think of a reason for using it. Using a symbol or string as the key is much more efficient as the program does not have to load the whole class. (a side note, as pointed by others, your example in fact is using a symbol key, and you'll need to use a hash rocket to use a class name as the key)

EJAg
  • 3,210
  • 2
  • 14
  • 22