How to Use Hash Keys as Methods on a Class

How do I use hash keys as methods on a class?

def method_missing(name, *args, &blk)
if args.empty? && blk.nil? && @attributes.has_key?(name)
@attributes[name]
else
super
end
end

Explanation: If you call a method, which does not exist, method_missing is invoked with the name of the method as the first parameter, followed by the arguments given to the method and the block if one was given.

In the above we say that if a method, which was not defined, is called without arguments and without a block and the hash has an entry with the method name as key, it will return the value of that entry. Otherwise it will just proceed as usual.

How do I convert hash keys to method names?

You could just wrap up your hash in an OpenStruct:

require 'ostruct'
tempData = {"a" => 100, "here" => 200, "c" => "hello"}
os = OpenStruct.new tempData
os.a #=> 100
os.here #=> 200

If you really really wanted to, you could also monkey-patch the Hash class, but I'd advise against that:

class Hash
def method_missing(m, *args, &blk)
fetch(m) { fetch(m.to_s) { super } }
end
end

tempData = {"a" => 100, "here" => 200, "c" => "hello"}
tempData.a #=> 100

Update: In my personal extensions library I added a Hash#to_ostruct method. This will recursively convert a hash into an OpenStruct including all nested hashes.

How to map hash keys to methods for an encapsulated Ruby class (tableless model)?

You have not initailized the class with the hash. You should do:

json = "{\"id\":2,\"firstname\":\"Name_test\",\"lastname\":\"Surname_test\"}"
hash = Users::Account.to_model(json)
account = Users::Account.new(hash)

Then account.id will give the value.

Hash keys as accessors in a class

Just a quick example. Do you have any reasons to not monkey-patch your Hash?

irb(main):001:0> class Hash
irb(main):002:1> def method_missing(name, *args, &blk)
irb(main):003:2> if self.keys.map(&:to_sym).include? name.to_sym
irb(main):004:3> return self[name.to_sym]
irb(main):005:3> else
irb(main):006:3* super
irb(main):007:3> end
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):012:0> h = {:hello => 'world'}
=> {:hello=>"world"}
irb(main):013:0> h.hello
=> "world"

Writing custom method for a hash on Ruby? on rails

Prepending self. to the method name makes it a class method instead of an instance method. If you are not sure of the difference, you should look it up as it is fundamental to properly defining and using classes and methods.

As a class method, you would use it as:

my_hash = MyHash.format_hash_data(my_hash)

Or if you're in scope of the class, simply my_hash = format_hash_data(my_hash), which is why it worked in your case with the self. prepended (class method definition).

If you want to define it as an instance method (a method that is defined for the instance), you would use it like so:

my_hash = my_hash.format_hash_data

And the definition would use the implicit self of the instance:

def format_hash_data
self.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end

object oriented perl, set hash values

Note   The short first part refers to the original question, which changed in the meanwhile.


When you are setting up the hash, you iterate over the array with id's, using $id_value as the variable. Then you assign it to a non-existent key

$self->{_devices}{ids} = $id_value;  # what is 'ids' string?

Thus a key 'ids' will be added and each assignment will overwrite the previous. Also, note that this way the $id_value (would) become values, not keys, contrary to what the question states.

What is supposed to be in $self->{_devices} ? If keys are id's, to be "initialized", say to 0

foreach my $id (@$id_ref)
{
push @{$self->{_ids}}, $id;
$self->{_devices}{$id} = 0;
}

There are more compact and clearer ways but let's first clarify if this is intended.


Update to clarification in comments

The objective is to construct a structure


_device --> id1 --> status --> 0/1
id2 --> status --> 0/1
...

We also want to be able to read/change values, and add another kind of 'status'. Let us first forget about classes and build this data structure.
See tutorial perlreftut and the data-structures cookbook perldsc.

In Perl we can use a hash reference for this, with further nested hash references

my $hashref = { 
'id1' => {
'status' => 0,
'status2' => 0
},
'id2' => {
'status' => 0,
'status2' => 0,
},
# ...
};

The anonymous hash { status => 0, status2 => 0 } is assigned as a value to keys 'id1', 'id2'. Being a (nameless) reference to a hash it is loosely called hashref (like \%hash is, too). Any reference is a scalar, a single value, so a hashref can be assigned to a key. This is how we build nested (complex) data structures, using references. They are much like pointers.

We can populate it in code like so

use warnings 'all';
use strict;
use Data::Dumper; # to see our structures

my @ids = qw(id1 id2 id3);

my $hashref;

foreach my $id (@ids) {
foreach my $stat ('status', 'status2') {
$hashref->{$id}{$stat} = 0;
}
}

print Dumper($hashref);

Strictly, we'd need to dereference at each level, $hashref->{$id}->{$stat}, but a notational shortcut allows us to omit all other than the first one (since that then can't mean anything else).

The keys $id are added to the hash once encountered (autovivified). So are the keys $stat in the nested hash(ref) (assigned the value 0 above). We print and change values by

$hashref->{$id}{'status'} = 1;

print "$hashref->{$id}{'status'}\n";

We can add yet another 'status' the same way, $hashref->{$id}{'status3'} = 0.

Now to the class. We use only one 'status' field. Please add suitable error checking.

sub Importids 
{
my ($self, $id_ref) = @_;

foreach my $id (@{$id_ref})
{
push @{$self->{_ids}}, $id;
$self->{_devices}{$id}{'status'} = 0;
}
return 1;
}

How do we change values for given ids? Let's first come up with an interface. With ids in $id variables and their new values-to-be in $val's, we can imagine a call

$obj->set_status( { $id1 => $val1, $id2 => $val2 } );             
# Or
$obj->set_status( $new_vals_for_ids_hashref );

This builds a hashref inside the call or uses one built previously.
Then here is a method for it

sub set_status 
{
my ($self, $rhstat) = @_; # check for $rhstat

foreach my $id (keys %$rhstat) {
$self->{_devices}{$id} = $rhstat->{$id};
}
return 1;
}

This adds new ids if they are not already there, or overwrites the values for the existing ones. We can make this same sub be the getter as well -- when nothing is passed in

sub status 
{
my ($self, $rstat) = @_;

return $self->{_devices} if not defined $rstat;

foreach my $id (keys %$rstat) {
$self->{_devices}{$id} = $rstat->{$id};
}
return $self->{_devices};
}

with intended uses my $devices = $obj->status(); or with an argument. Since we now return data in one case we do the same when ids are added/changed as well, for interface consistency.

You can add 'status' fields similarly,

$obj->add_status( [ qw(status2 status3) ] );

where [ ... ] is an anonymous array, with a method

sub add_status 
{
my ($self, $rstats) = @_;

foreach my $stat (@$rstats)
{
foreach my $id (@{$self->{_ids}})
{
$self->{_devices}{$id}{$stat} = 0;
}
}
return 1;
}

We can make this method also optionally take and set values for new keys, by passing either a hashref with key-value pairs (add keys and set them), or an arrayref as above, what can be checked in the method (see ref).

Note that there are more compact ways to work with lists of keys/values.

How to make object instance a hash key in Ruby?

See http://ruby-doc.org/core/classes/Hash.html

Hash uses key.eql? to test keys for
equality. If you need to use instances
of your own classes as keys in a Hash,
it is recommended that you define both
the eql? and hash methods. The hash
method must have the property that
a.eql?(b) implies a.hash == b.hash.

The eql? method is easy to implement: return true if all member variables are the same. For the hash method, use [@data1, @data2].hash as Marc-Andre suggests in the comments.



Related Topics



Leave a reply



Submit