Suppose I have a w_data hash
{"latitude"=>"40.695", "air_temperature"=>"-10", "longitude"=>"-96.854", "datetime"=>"2014-01-01 02:55:00"}
I want to access it's value by w_data.latitude rather than w_data["latitude"]
How to do it?
I'm going to say don't use OpenStruct, because it nukes the method cache every time you create a new one.
Instead, consider a gem like hashie-mash, or roll your own hash-alike:
Hashie::Mash:
hsh = Hashie::Mash.new("latitude"=>"40.695", "air_temperature"=>"-10", "longitude"=>"-96.854", "datetime"=>"2014-01-01 02:55:00")
hsh.latitude
=> "40.695"
Custom solution:
class AccessorHash < Hash
def method_missing(method, *args)
s_method = method.to_s
if s_method.match(/=$/) && args.length == 1
self[s_method.chomp("=")] = args[0]
elsif args.empty? && key?(s_method)
self.fetch(s_method)
elsif args.empty? && key?(method)
self.fetch(method)
else
super
end
end
end
hsh = AccessorHash.new("latitude"=>"40.695", "air_temperature"=>"-10", "longitude"=>"-96.854", "datetime"=>"2014-01-01 02:55:00")
hsh.latitude # => "40.695"
hsh.air_temperature = "16"
hsh => # {"latitude"=>"40.695", "air_temperature"=>"16", "longitude"=>"-96.854", "datetime"=>"2014-01-01 02:55:00"}
If you want a pure Ruby solution, just crack open the Hash class and upgrade the method_missing method!
class Hash
def method_missing method_name, *args, &block
return self[method_name] if has_key?(method_name)
return self[$1.to_sym] = args[0] if method_name.to_s =~ /^(.*)=$/
super
end
end
Now, every hash has this ability.
hash = {:five => 5, :ten => 10}
hash[:five] #=> 5
hash.five #=> 5
hash.fifteen = 15
hash[:fifteen] #=> 15
hash.fifteen #=> 15
method_missing is available in every Ruby class, to catch attempted calls for methods that don't (yet) exist. I've turned this into a blog post (with interactive Codewars kata) here:
http://www.rubycuts.com/kata-javascript-object
Convert hash into an OpenStruct. Here:
require 'ostruct'
w_data = OpenStruct.new
hash = {"latitude"=>"40.695", "air_temperature"=>"-10", "longitude"=>"-96.854", "datetime"=>"2014-01-01 02:55:00"}
w_data.marshal_load(hash)
w_data.longitude
#=> "-96.854"
Another simpler way:
require 'ostruct'
hash = {"latitude"=>"40.695", "air_temperature"=>"-10", "longitude"=>"-96.854", "datetime"=>"2014-01-01 02:55:00"}
w_data = OpenStruct.new(hash)
w_data.longitude
#=> "-96.854"
Related
Here is my problem. I like Andrea Pavoni's way of allowing a nested hash to be used to initialize a class.
require 'ostruct'
class DeepStruct < OpenStruct
def initialize(hash=nil)
#table = {}
#hash_table = {}
if hash
hash.each do |k,v|
#table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
#hash_table[k.to_sym] = v
new_ostruct_member(k)
end
end
end
def to_h
#hash_table
end
end
But I can't find a way to include a hash (in the class) with specific default values, so that the behavior would be as follows:
Original behavior without default (with above code):
input_hash = {a: {b: 1}}
new_object = DeepStruct.new hash
new_object.a # => #<DeepStruct b=1>
new_object.a.b # => 1
new_object.a.to_h # => {b: 1}
With the following default_h defined inside the class:
default_h = {a: {dc: 2}, dd: {de: 4}}
input_hash and default_h should merge as follows
(actually using deep_merge for nested hash)
{:a=>{:dc=>2, :b=>1}, :dd=>{:de=>4}}
The behavior with default hash should be:
new_object = DeepStruct.new hash
new_object.a.b # => 1
new_object.a.dc # => 2
new_object.a.to_h # => {:dc=>2, :b=>1}
I can't find a way to implement this behavior inside the class. I would really appreciate any help in this matter.
Edit: Now trying to use David's code in a class:
class CompMedia
require 'ostruct'
attr_accessor :merged_h
def initialize(hash)
defaults = {a: {dc: 2}, dd: {de: 4}}
#merged_h = {}
deep_update(merged_h, defaults)
deep_update(merged_h, hash)
#merged_h
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def deep_open_struct(hash)
result = OpenStruct.new
hash.each do |key, value|
if value.is_a?(Hash)
result[key] = deep_open_struct(value)
else
result[key] = value
end
end
result
end
end # class CompMedia
input_hash = {a: {b: 1}}
cm = CompMedia.new(input_hash)
object = cm.deep_open_struct(cm.merged_h)
p object.marshal_dump # {:a=>#<OpenStruct dc=2, b=1>, :dd=>#<OpenStruct de=4>}
p object.a # <OpenStruct dc=2, b=1>
p object.a.marshal_dump # {:dc=>2, :b=>1}
p object.a.b # 1
p object.a.dc # 2
p object.dd # <OpenStruct de=4>
Obviously, I haven't found a way to retrieve in a simple fashion the nested hash elements from the openstruct object.
My objective is to create a class that would be initialized with a default (nested) hash contained in the class, and a (nested) input hash. In addition, I want to be able to add methods that would process the hash inside the class. I am not there yet.
On the other hand, I could just use the merged hash and this would work albeit with slightly more cumbersome notations:
class CompMedia
attr_accessor :merged_h
def initialize(hash)
defaults = {a: {dc: 2}, dd: {de: 4}}
#merged_h = {}
deep_update(merged_h, defaults)
deep_update(merged_h, hash)
#merged_h
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def multiply_by(k)
merged_h[:a][:dc] * k
end
end
input_hash = {a: {b: 1}}
cm = CompMedia.new(input_hash)
p cm.merged_h # {:a=>{:dc=>2, :b=>1}, :dd=>{:de=>4}}
p cm.merged_h[:a] # {:dc=>2, :b=>1}
p cm.merged_h[:a][:dc] # 2
p cm.merged_h[:dd] # {:de=>4}
p cm.multiply_by(10) # 20
I will consider the last version as my solution unless someone can make the code with OpenStruct work, which I would prefer.
Here is some code that does what you want, except I threw away the idea of subclassing OpenStruct because I wasn't sure if it was a good idea. Also, I implemented deep_merge myself because it was pretty easy to do, but you could try using the version from ActiveSupport if you wanted.
require 'ostruct'
# Merges two hashes that could have hashes inside them. Default
# values/procs of the input hashes are ignored. The output hash will
# not contain any references to any of the input hashes, so you don't
# have to worry that mutating the output will affect the inputs.
def deep_merge(h1, h2)
result = {}
deep_update(result, h1)
deep_update(result, h2)
result
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def deep_open_struct(hash)
result = OpenStruct.new
hash.each do |key, value|
if value.is_a?(Hash)
result[key] = deep_open_struct(value)
else
result[key] = value
end
end
result
end
input_hash = {a: {b: 1}}
defaults = {a: {dc: 2}, dd: {de: 4}}
object = deep_open_struct(deep_merge(defaults, input_hash))
p object.a.b
p object.a.dc
p object.a.to_h
I am trying to compose an object Transaction from objects TranFee and Rate.
class Transaction
attr_reader :tranfee, :rate
def initialize(hash)
#tranfee = PaymentType::TranFee.new(hash)
#rate = PaymentType::Rate.new(hash)
end
end
module PaymentType
def initialize(args = {}, regex)
args.each do |key,value|
if key =~ regex
instance_variable_set("##{key}", value) unless value.nil?
eigenclass = class << self; self; end
eigenclass.class_eval do
attr_reader key
end
end
end
end
class TranFee
include PaymentType
def initialize(args, regex = /\Atran.*/)
super(args, regex)
end
end
class Rate
include PaymentType
def initialize(args, regex = /\Arate.*/)
super(args, regex)
end
end
end
The rate and TranFee objects are created from a hash like the one below.
reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005,
"tran_fee" => 0.21, "rate_basis_points" => 0.002, "tran_auth_fee" => 0.10}
I am initializing the objects based on regex because the hash will eventually contain more values and I want the program to adjust as more items/classes are added.
Additionally there will be some instances where there are no key's starting with "tran". Does anyone know how to make Transaction create only a Rate object if TranFee has no instance variables inside of it? (in otherwords, if the hash returns nothing when keys =~ /\Atran.*/)
an example would be when the hash looks like this reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005, "rate_basis_points" => 0.002}, right now the output is
#<Transaction:0x007ff98c070548 #tranfee=#<PaymentType::TranFee:0x007ff98c070520>, #rate=#<PaymentType::Rate:0x007ff98c0704a8 #rate_base=0.0005, #rate_basis_points=0.002>>
So I am getting a TranFee object with nothing in it and I would like for that to drop off in this situation. not sure if there may be a better way to design this? I was trying to think of a way to use ostruct or struct, but I havnt been able to figure it out. thanks for any help here.
I believe your strategy is very problematic - creating attributes to a class from user input doesn't sound like a very good idea.
Furthermore, adding methods (like attr_reader) to every instances can have severe performance issues.
If all you want is a data structure to hold your data, keep using a Hash. If you want a structure you can query using a dot notation instead of bracket notation, you might want to consider a gem like hashie or hashr.
If you want some code to make the flat data-structure hierarchical, I can suggest something like this:
hierarchical_hash = hash.each_with_object({}) do |(k, v), h|
if k.match(/^([^_]+)_(.+)$/)
root_key = $1
child_key = $2
h[root_key] ||= {}
h[root_key][child_key] = v
else
h[k] = v
end
end
# => {
# => "name" => "reg_debit",
# => "rate" => {
# => "base" => 0.0005,
# => "basis_points" => 0.002
# => },
# => "tran" => {
# => "fee" => 0.21,
# => "auth_fee" => 0.1
# => }
# => }
Your question raises some interesting issues. I will try to explain how you can fix it, but, as #Uri mentions, there may be better ways to address your problem.
I've assumed #tranfee is to be set equal to the first value in the hash whose key begins with "tran" and that #rate is to be set equal to the first value in the hash whose key begins with "rate". If that interpretation is not correct, please let me know.
Note that I've put initialize in the PaymentType module in a class (Papa) and made TranFee and Rate subclasses. That's the only way you can use super within initialize in the subclasses of that class.
Code
class Transaction
attr_reader :tranfee, :rate
def initialize(hash={})
o = PaymentType::TranFee.new(hash)
#tranfee = o.instance_variable_get(o.instance_variables.first)
o = PaymentType::Rate.new(hash)
#rate = o.instance_variable_get(o.instance_variables.first)
end
end
.
module PaymentType
class Papa
def initialize(hash, prefix)
key, value = hash.find { |key,value| key.start_with?(prefix) && value }
(raise ArgumentError, "No key beginning with #{prefix}") unless key
instance_variable_set("##{key}", value)
self.class.singleton_class.class_eval { attr_reader key }
end
end
class TranFee < Papa
def initialize(hash)
super hash, "tran"
end
end
class Rate < Papa
def initialize(hash)
super hash, "rate"
end
end
end
I believe the method Object#singleton_class has been available since Ruby 1.9.3.
Example
reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005, "tran_fee" => 0.21,
"rate_basis_points" => 0.002, "tran_auth_fee" => 0.10}
a = Transaction.new reg_debit
p Transaction.instance_methods(false) #=> [:tranfee, :rate]
p a.instance_variables #=> [:#tranfee, :#rate]
p a.tranfee #=> 0.21
p a.rate #=> 0.0005
This method takes a hash and returns a new hash without sensitive information. It does not modify the hash passed in.
Is there a more Ruby-like, idiomatic way of doing it?
def sanitize hash
new_hash = hash.dup
protected_keys = [ :password, 'password', :confirmation, 'confirmation' ]
new_hash.each do |k,v|
if protected_keys.include?( k ) && ! v.blank?
new_hash[ k ] = 'xxxxxxxxx'
end
end
new_hash
end
Working in Ruby 1.9.3, Sinatra (not Rails) and not using Active Record.
Perhaps something like:
class Hash
def sanitize(*keys)
new_hash = self.dup
new_hash.each do |k,v|
if keys.include?(k) && ! v.empty?
new_hash[k] = 'xxxxxxxxxx'
end
end
end
def sanitize!(*keys)
self.each do |k,v|
if keys.include?(k) && ! v.empty?
self[k] = 'xxxxxxxxxx'
end
end
end
end
You can then call
hash = {password: "test", name: "something"}
sanitized_hash = hash.sanitize(:password, 'password', :confirmation, 'confirmation')
And then sanitize! will modify the Hash in place without duping per Ruby standards.
It is inefficient to iterate over the protected keys for each key in the hash as in your solution. Rather, just iterate over the protected keys.
It is inefficient to generate the array of protected keys each time the method is called. Define that array outside of the method.
The following is better in these respects:
ProtectedKeys = %w[password confirmation]
def sanitize hash
new_hash = hash.dup
ProtectedKeys.each do |k| [k, k.to_sym].each do |k|
new_hash[k] = "xxxxxxxxx" if new_hash.key?(k) and new_hash[k].present?
end end
new_hash
end
And another one:
def sanitize(params)
protected_keys = %(password confirmation)
replacement = 'xxxxxx'
new_params = params.dup
new_params.each_key {|key| new_params[key] = replacement if protected_keys.include?(key.to_s)}
end
test_hash = {
name: 'Me',
password: 'secret',
address: 'Here',
confirmation: 'secret'
}
puts sanitize(test_hash)
How do I call a method, given its name, on an element of an array?
For example, I could have:
thing = "each"
I want to be able to do something like:
def do_thing(thing)
array = [object1,object2]
array[0].thing
end
so that do_thing(to_s), for example, would run object1.to_s.
You can use public_send or send. public_send only sends to public methods while send can see public and private methods.
def do_thing(thing)
array = [1,2,3]
array.public_send(thing)
end
do_thing('first')
# => 1
do_thing(:last)
# => 3
Update A more general version:
def do_thing(array, index, method, *args)
array[index].public_send(method, *args)
end
do_thing([1, 2, 3], 0, :to_s)
# => "1"
do_thing([[1,2], [3, 4]], 0, :fetch, 0)
# => 1
require 'ostruct'
o = OpenStruct.new(attribute: 'foo')
do_thing([o], 0, :attribute=, 'bar')
o.attribute == 'bar'
# => true
Object#send
thing = "each"
def do_thing(thing)
array = [1,2,3]
array.send(thing)
end
From the doc:
class Klass
def hello(*args)
"Hello " + args.join(' ')
end
end
k = Klass.new
k.send :hello, "gentle", "readers" #=> "Hello gentle readers"
Here is an example to help you out although I don't have any idea what objects are residing inside your array:
arr = [Array.new(2,10),"abc" ]
arr.each{|i| p i.send(:length)}
#>>2
#>>3
There is a common idiom of using substitutions like:
def with clazz, &block
yield clazz
clazz
end
with Hash.new |hash|
hash.merge!{:a => 1}
end
Is there a way to go further and define #with to have a possibility of doing:
with Hash.new |hash|
merge!{:a => 1}
end
or even:
with Hash.new do
merge!{:a => 1}
end
?
UPDATE
Later accidentally I found exactly what I was looking for (solution similar to the accepted one):
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/19153
UPDATE 2
It was added to sugar-high/dsl in https://github.com/kristianmandrup/sugar-high
UPDATE 3
docille project on Github exploits this idea very nicely.
If you are referring to the way in which Rails does routing then I think you need to do something like this
def with(instance, &block)
instance.instance_eval(&block)
instance
end
with(Hash.new) do
merge!({:a => 1})
merge!({:b => 1})
end
This is how I can see it being done in the Rails source anyway start by looking at the draw method in action_pack/lib/action_dispatch/routing/route_set
Isn't your pseudo-Ruby:
with Hash.new do |hash|
merge!{:a => 1}
end
The same thing as using 1.9's tap? For example:
>> x = Hash[:a, :b].tap { |h| h.merge!({:c => :d}) }
=> {:a=>:b, :c=>:d}
You still have to name the block argument of course.
You can use the ruby builtin tap:
Hash.new.tap do |hash|
hash.merge! a: 1
end
This can even be "abused" for multiple objects:
[one_long_name, another_long_name].tap do |(a,b)|
a.prop = b.prop
end
Of course both don't give you exactly what with would do according to your example: The block won't be evaluated within the instance of the object. But I prefer a lot to use tap with multiple objects, plus tap return self, so it can be chained:
[one_long_name, another_long_name].tap {|(a,b)| a.prop = b.prop }.inspect