PStore is a data-serializer in the Stdlib which is human-readable like JSON or YAML.
Everything has to be done in a "transaction", e.g.
storage = PStore.new("db")
storage.transaction do |db|
db[:key] = {}
db[:key][:sub_key] = "val"
return db[:key][:sub_key]
end
# ==> "val"
I want to be able to make a method like this:
def store(storage, list_of_keys, value)
storage.transaction do |db|
db[list_of_keys[0][list_of_keys[1]][list_of_keys[n]] = value
end
end
Conceptually I think currying would be useful here, but I've only done it in Javascript.
It doesn't seem to work to store the references to the intermediate values in variables like this:
storage.transaction do |db|
db_val_1 = db[keys[0]]
db_val_1 = value
end
I don't know exactly what you want. But my guess is you wanna store a value inside a nested hash with given list of keys. Since it's a nested has, I would use, well, a hash obviously. Here is a full working code for what I think you are trying to do:
require 'pstore'
require 'minitest/autorun'
storage = PStore.new("data_file.pstore")
def nest(key_list, value)
reverse_key_list = key_list.reverse
inner_most = {"#{reverse_key_list.shift}" => value}
reverse_key_list.reduce(inner_most) {|acc, current| {"#{current}" => acc }}
end
def nested_store(storage, key_list, value)
root = key_list.shift
data = nest(key_list, value)
storage.transaction { storage[root] = data }
end
describe "pstore test" do
before do
#key_list = %w(root sub1 sub2 sub3)
#val = "value"
end
it "should nest the list" do
nest(#key_list, #val).must_equal({"root" => {"sub1" => {"sub2" => {"sub3" => "value" }}}})
end
it "store the nested list" do
storage = PStore.new("data_file.pstore")
nested_store(storage, #key_list, #val)
storage.transaction do
storage["root"]["sub1"]["sub2"]["sub3"].must_equal "value"
end
end
end
Related
I have a Hash which needs to be converted in a String with escaped characters.
{name: "fakename"}
and should end up like this:
'name:\'fakename\'
I don't know how this type of string is called. Maybe there is an already existing method, which I simply don't know...
At the end I would do something like this:
name = {name: "fakename"}
metadata = {}
metadata['foo'] = 'bar'
"#{name} AND #{metadata}"
which ends up in that:
'name:\'fakename\' AND metadata[\'foo\']:\'bar\''
Context: This query a requirement to search Stripe API: https://stripe.com/docs/api/customers/search
If possible I would use Stripe's gem.
In case you can't use it, this piece of code extracted from the gem should help you encode the query parameters.
require 'cgi'
# Copied from here: https://github.com/stripe/stripe-ruby/blob/a06b1477e7c28f299222de454fa387e53bfd2c66/lib/stripe/util.rb
class Util
def self.flatten_params(params, parent_key = nil)
result = []
# do not sort the final output because arrays (and arrays of hashes
# especially) can be order sensitive, but do sort incoming parameters
params.each do |key, value|
calculated_key = parent_key ? "#{parent_key}[#{key}]" : key.to_s
if value.is_a?(Hash)
result += flatten_params(value, calculated_key)
elsif value.is_a?(Array)
result += flatten_params_array(value, calculated_key)
else
result << [calculated_key, value]
end
end
result
end
def self.flatten_params_array(value, calculated_key)
result = []
value.each_with_index do |elem, i|
if elem.is_a?(Hash)
result += flatten_params(elem, "#{calculated_key}[#{i}]")
elsif elem.is_a?(Array)
result += flatten_params_array(elem, calculated_key)
else
result << ["#{calculated_key}[#{i}]", elem]
end
end
result
end
def self.url_encode(key)
CGI.escape(key.to_s).
# Don't use strict form encoding by changing the square bracket control
# characters back to their literals. This is fine by the server, and
# makes these parameter strings easier to read.
gsub("%5B", "[").gsub("%5D", "]")
end
end
params = { name: 'fakename', metadata: { foo: 'bar' } }
Util.flatten_params(params).map { |k, v| "#{Util.url_encode(k)}=#{Util.url_encode(v)}" }.join("&")
I use it now with that string, which works... Quite straigt forward:
"email:\'#{email}\'"
email = "test#test.com"
key = "foo"
value = "bar"
["email:\'#{email}\'", "metadata[\'#{key}\']:\'#{value}\'"].join(" AND ")
=> "email:'test#test.com' AND metadata['foo']:'bar'"
which is accepted by Stripe API
I wrote weird, little and simple DSL on ruby, and I stuck on output values of keys.
I need to output values of concrete key of hash by that command:
p config.key1
#> Output down below:
#> "value1"
# here's key1 is key of hash, i want to output value of key that i wrote in method call.
# p config.key_name for example
There's my configus realisation:
require "deep_merge/rails_compat"
class Configus
class InHash
attr_reader :inner_hash
def initialize
#inner_hash = {}
end
def method_missing(name, *args, &block)
if block_given?
context = InHash.new
context.instance_eval &block
result = context.inner_hash
else
result = args
end
#inner_hash[name] = result
end
end
def self.config(environment, parent = nil, &block)
in_hash = InHash.new
in_hash.instance_eval &block
keys = in_hash.inner_hash.keys
index = keys.find_index(environment)
if parent && environment
parent_hash = in_hash.inner_hash[parent]
adopted_hash = in_hash.inner_hash[environment]
merged_hash = parent_hash.deeper_merge!(adopted_hash, { :overwrite_arrays => "TRUE" })
elsif environment == keys[index]
"#{environment.capitalize} hash: " + in_hash.inner_hash[environment]
end
end
end
Here's my init.rb:
require "./configus"
require "pry"
config = Configus.config :staging, :production do
production do
key1 "value1"
key2 "value2"
group1 do
key3 "value3"
key4 "value4"
end
end
staging do
key2 "new value2"
group1 do
key4 "new value4"
end
end
development do
key1 "new value1"
key2 "value2"
group1 do
key3 "new value3"
key4 "value4"
end
end
productionisgsgsd do
key10 "value10"
end
end
puts config.key1
#> I wrote output down below:
Traceback (most recent call last):
init.rb:35:in `<main>': undefined method `key1' for #<Hash:0x000056261379cdb0> (NoMethodError)
Did you mean? key
key?
keys
I just want to output value of concrete key of hash with concrete command:
p config.key_name
But i don't know how to do that and because of my stupid problem i want to ask you guys.
P.S Sorry for my english skill and that weird realisation of dsl ¯\_(ツ)_/¯
The issue is that you return a normal hash from your Configus.config method. Instead you should return your special InHash instance which you're able to customize behaviour.
The current issue lies here:
def self.config(environment, parent = nil, &block)
in_hash = InHash.new
in_hash.instance_eval &block
keys = in_hash.inner_hash.keys
index = keys.find_index(environment)
if parent && environment
parent_hash = in_hash.inner_hash[parent]
adopted_hash = in_hash.inner_hash[environment]
merged_hash = parent_hash.deeper_merge!(adopted_hash, { :overwrite_arrays => "TRUE" })
# ^ the above line returns a normal hash from the method
in_hash # <= add this to return your in_hash object
elsif environment == keys[index]
"#{environment.capitalize} hash: " + in_hash.inner_hash[environment]
end
end
The problem is not entirely solved with the above, since this now returns your InHash instance, but your InHash has no way of fetching keys, you can only set them. So you want to add a way to fetch values:
def method_missing(name, *args, &block)
if block_given?
context = InHash.new
context.instance_eval &block
#inner_hash[name] = context # set to the nested InHash instance
elsif args.empty?
#inner_hash[name] # without arguments given read the value
else
#inner_hash[name] = args # set to the arguments provided
end
end
The following code:
config = Configus.config :staging, :production do
# ...
end
Should now set config to an InHash instance, that has your custom behaviour defined. The args.empty? check in method_missing will return a value if no further arguments are given. Meaning that:
config.key1
Should now return your ["value1"] value. #inner_hash[name] = args will always set the value to an array. If you don't want this you might want to change it to:
#inner_hash[name] = args.size > 1 ? args : args.first
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
I need to populate a Hash with various values. Some of values are accessed often enough and another ones really seldom.
The issue is, I'm using some computation to get values and populating the Hash becomes really slow with multiple keys.
Using some sort of cache is not a option in my case.
I wonder how to make the Hash compute the value only when the key is firstly accessed and not when it is added?
This way, seldom used values wont slow down the filling process.
I'm looking for something that is "kinda async" or lazy access.
There are many different ways to approach this. I recommend using an instance of a class that you define instead of a Hash. For example, instead of...
# Example of slow code using regular Hash.
h = Hash.new
h[:foo] = some_long_computation
h[:bar] = another_long_computation
# Access value.
puts h[:foo]
... make your own class and define methods, like this...
class Config
def foo
some_long_computation
end
def bar
another_long_computation
end
end
config = Config.new
puts config.foo
If you want a simple way to cache the long computations or it absolutely must be a Hash, not your own class, you can now wrap the Config instance with a Hash.
config = Config.new
h = Hash.new {|h,k| h[k] = config.send(k) }
# Access foo.
puts h[:foo]
puts h[:foo] # Not computed again. Cached from previous access.
One issue with the above example is that h.keys will not include :bar because you haven't accessed it yet. So you couldn't, for example, iterate over all the keys or entries in h because they don't exist until they're actually accessed. Another potential issue is that your keys need to be valid Ruby identifiers, so arbitrary String keys with spaces won't work when defining them on Config.
If this matters to you, there are different ways to handle it. One way you can do it is to populate your hash with thunks and force the thunks when accessed.
class HashWithThunkValues < Hash
def [](key)
val = super
if val.respond_to?(:call)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
end
h = HashWithThunkValues.new
# Populate hash.
h[:foo] = ->{ some_long_computation }
h[:bar] = ->{ another_long_computation }
h["invalid Ruby name"] = ->{ a_third_computation } # Some key that's an invalid ruby identifier.
# Access hash.
puts h[:foo]
puts h[:foo] # Not computed again. Cached from previous access.
puts h.keys #=> [:foo, :bar, "invalid Ruby name"]
One caveat with this last example is that it won't work if your values are callable because it can't tell the difference between a thunk that needs to be forced and a value.
Again, there are ways to handle this. One way to do it would be to store a flag that marks whether a value has been evaluated. But this would require extra memory for every entry. A better way would be to define a new class to mark that a Hash value is an unevaluated thunk.
class Unevaluated < Proc
end
class HashWithThunkValues < Hash
def [](key)
val = super
# Only call if it's unevaluated.
if val.is_a?(Unevaluated)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
end
# Now you must populate like so.
h = HashWithThunkValues.new
h[:foo] = Unevaluated.new { some_long_computation }
h[:bar] = Unevaluated.new { another_long_computation }
h["invalid Ruby name"] = Unevaluated.new { a_third_computation } # Some key that's an invalid ruby identifier.
h[:some_proc] = Unevaluated.new { Proc.new {|x| x + 2 } }
The downside of this is that now you have to remember to use Unevaluted.new when populating your Hash. If you want all values to be lazy, you could override []= also. I don't think it would actually save much typing because you'd still need to use Proc.new, proc, lambda, or ->{} to create the block in the first place. But it might be worthwhile. If you did, it might look something like this.
class HashWithThunkValues < Hash
def []=(key, val)
super(key, val.respond_to?(:call) ? Unevaluated.new(&val) : val)
end
end
So here is the full code.
class HashWithThunkValues < Hash
# This can be scoped inside now since it's not used publicly.
class Unevaluated < Proc
end
def [](key)
val = super
# Only call if it's unevaluated.
if val.is_a?(Unevaluated)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
def []=(key, val)
super(key, val.respond_to?(:call) ? Unevaluated.new(&val) : val)
end
end
h = HashWithThunkValues.new
# Populate.
h[:foo] = ->{ some_long_computation }
h[:bar] = ->{ another_long_computation }
h["invalid Ruby name"] = ->{ a_third_computation } # Some key that's an invalid ruby identifier.
h[:some_proc] = ->{ Proc.new {|x| x + 2 } }
You can define your own indexer with something like this:
class MyHash
def initialize
#cache = {}
end
def [](key)
#cache[key] || (#cache[key] = compute(key))
end
def []=(key, value)
#cache[key] = value
end
def compute(key)
#cache[key] = 1
end
end
and use it as follows:
1.9.3p286 :014 > hash = MyHash.new
=> #<MyHash:0x007fa0dd03a158 #cache={}>
1.9.3p286 :019 > hash["test"]
=> 1
1.9.3p286 :020 > hash
=> #<MyHash:0x007fa0dd03a158 #cache={"test"=>1}>
you can use this:
class LazyHash < Hash
def [] key
(_ = (#self||{})[key]) ?
((self[key] = _.is_a?(Proc) ? _.call : _); #self.delete(key)) :
super
end
def lazy_update key, &proc
(#self ||= {})[key] = proc
self[key] = proc
end
end
Your lazy hash will behave exactly as a normal Hash, cause it is actually a real Hash.
See live demo here
*** UPDATE - answering to nested procs question ***
Yes, it would work, but it is cumbersome.
See updated answer.
Use lazy_update instead of []= to add "lazy" values to your hash.
This isn't strictly an answer to the body of your question, but Enumerable::Lazy will definitely be a part of Ruby 2.0. This will let you do lazy evaluation on iterator compositions:
lazy = [1, 2, 3].lazy.select(&:odd?)
# => #<Enumerable::Lazy: #<Enumerator::Generator:0x007fdf0b864c40>:each>
lazy.to_a
# => [40, 50]
#hash is a global hash that looks something like:
#hash = {
"xmlns:xsi" =>"http://www.w3.org/2001/XMLSchema-instance",
"xsi:noNamespaceSchemaLocation"=>"merchandiser.xsd",
"header" =>[{"merchantId"=>["35701"],
"merchantName" =>["Lingerie.com"],
"createdOn" =>["2011-09-23/00:33:35"]}],
"trailer" =>[{"numberOfProducts"=>["0"]}]
}
And I would expect this to work if I call the method below like:
def amethod
hash_value("header", "merchantName") // returns "Lingerie.com"
end
def hash_value *attributes, hash = nil
hash = #hash unless hash
att = attributes.delete_at.first
attributes.empty? ? hash[att].first : hash_value(attributes, hash[att].first)
end
You can't have a default argument after a splat arg. Instead, require that the attribute list be passed as an array, like so:
def hash_value(attributes, hash = #hash)
return hash if attributes.empty?
hash_value(attributes[1..-1], hash[attributes.first].first)
end
p hash_value(["header", "merchantName"]) # => "Lingerie.com"
p hash_value(["trailer", "numberOfProducts"]) # => "0"
Try this:
def hash_value(*attributes, hash)
hash = #hash unless hash
att = attributes.delete_at.first
attributes.empty? ? hash[att].first : hash_value(attributes, hash[att].first)
end