String to_i or to_f dynamically - ruby

I'm dynamically creating attr_readers and instance variables based on a response_header received from an API.
The response_header hash comes back like this:
{
"server"=>"nginx",
"x-ua-compatible"=>"IE=edge,chrome=1",
"expires"=>"Thu, 01 Jan 1970 00:00:00 GMT",
"cache-control"=>"no-cache, must-revalidate",
"pragma"=>"no-cache",
"rate-limit"=>"1000",
"rate-limit-remaining"=>"986",
"rate-limit-reset"=>"1265",
"content-type"=>"application/json;charset=UTF-8",
"content-language"=>"en",
"transfer-encoding"=>"chunked",
"vary"=>"Accept-Encoding",
"date"=>"Fri, 23 Jan 2015 15:38:56 GMT",
"connection"=>"close",
"x-frame-options"=>"SAMEORIGIN"
}
This get's sent into a class called ResponseHeader
class ResponseHeader
def initialize(options = {})
options.each do |key, value|
instance_variable_set("##{key}", value)
self.send(:attr_reader, key)
end
end
end
This is working pretty well except for the fact that rate-limit, rate-limit-remaining and rate-limit-reset are clearly integers that came back as strings from the API.
I'd like for my class to be intelligent here and change those values to integers but the only method I've come up with thus far feels very un-ruby.
Basically I'm converting to an integer and then back to a string and seeing if it equals the original value.
[8] pry(main)> "ABC".to_i
=> 0
[9] pry(main)> _.to_s
=> "0"
[10] pry(main)> _ == "ABC"
=> false
[11] pry(main)> "100".to_i
=> 100
[12] pry(main)> _.to_s
=> "100"
[13] pry(main)> _ == "100"
=> true
Is this my only option?

Ruby Integer parses a string to an integer and raises an error if the parse fails:
i = Integer("123") #=> 1234
i = Integer("abc") #=> ArgumentError: invalid value for Integer(): "abc"
Heads up that binary, octal, and hex are also parsed:
i = Integer("0b10") #=> 2
i = Integer("010") #=> 8
i = Integer("0x10") #=> 16
To parse base 10, provide the base:
i = Integer("010",10) #=> 10
To match within a string, use a regexp:
s = "foo123bar"
i = s=~/-?\d+/ ? Integer($&,10) : nil #=> 123
To patch the String class, define a new method:
class String
def match_integer
self=~/-?\d+/ ? Integer($&,10) : nil
end
end
"foo123bar".match_integer #=> 123

Related

Why is ruby acting like passing by reference when using gsub function in Ruby? [duplicate]

This question already has answers here:
Ruby 'pass by value' clarification [duplicate]
(3 answers)
Closed 4 years ago.
Given the following two methods:
[53] pry(main)> def my_method
[53] pry(main)* leti = 'leti'
[53] pry(main)* edit(leti)
[53] pry(main)* leti
[53] pry(main)* end
=> :my_method
[54] pry(main)> def edit(a_leti)
[54] pry(main)* a_leti.gsub!('e', '3')
[54] pry(main)* a_leti
[54] pry(main)* end
=> :edit
[55] pry(main)> my_method
=> "l3ti"
Can someone explain why I am getting the value edited inside the edit method and not the original value ('leti'). I though Ruby was passed by value. In fact, if instead of using the function gsub I use a simple assignment, I get the original value. Does the gsub! make it by reference?
Thank you!
In Ruby: Objects like strings are passed by reference. Variables with objects like strings are in fact references to those strings. Parameters are passed by value. However, for strings, these are references to those strings.
So here is the classic example:
irb(main):004:0* a = "abcd"
=> "abcd"
irb(main):005:0> b = a
=> "abcd"
irb(main):006:0> b << "def"
=> "abcddef"
irb(main):007:0> a
=> "abcddef"
irb(main):008:0> b
=> "abcddef"
If you do not wish to modify the original string, you need to make a copy of it:
Three ways (of many) to do this are:
b = a.dup
b = a.clone
b = String.new a
Using dup
irb(main):009:0> a = "abcd"
=> "abcd"
irb(main):010:0> b = a.dup
=> "abcd"
irb(main):011:0> b << "def"
=> "abcddef"
irb(main):012:0> a
=> "abcd"
irb(main):013:0> b
=> "abcddef"
BTW: For myself, this effect is the number one cause of defects in my own code.

JSON with symbols and strings not readable

I have the following JSON:
{ :a => 1, "b" => "test" }
jsonObject[:b] does not give me any data, whereas for a JSON with all keys as strings,
{ "a" => 1, "b" => "test" }
it works fine:
jsonObject[:b] # => "test"
Is there a constraint against using a symbol and key in the same JSON object?
I suggest to parse a JSON to a Hash before using, like
require 'json'
JSON.parse("{...}")
and convert a hash to a JSON string by
hash.to_json
all keys of symbols and strings are converted into strings.
require 'json'
a = {:a => '12', 'b' => '23'}
p aa = a.to_json #=> "{\"a\":\"12\",\"b\":\"23\"}"
p JSON.parse(aa) #=> {"a"=>"12", "b"=>"23"}
It might be possible that you are sometimes dealing with a simple Hash and sometimes with a HashWithIndifferentAccess. The Rails' params for example allow indifferent access by default. This might explain your confusion:
hash = { :a => 1, 'b' => 2 }
hash[:a]
#=> 1
hash['b']
#=> 2
hash[:b]
#=> nil
But with a HashWithIndifferentAccess:
hash = hash.with_indifferent_access
hash[:a]
#=> 1
hash['b']
#=> 2
hash[:b]
#=> 2

Round-trip JSON serialization in Ruby

Suppose I have a simple class
class Person
attr_accessor :name
def say
puts name
end
end
Is there a way to serialize it to JSON and back and get instance of the same class?
For example I would like to have a code like
p = Person.new
p.name = 'bob'
json = JSON.serialize p
# json should be smth. containing { 'name' : 'bob' }
# and maybe some additional information required for later deserialization
p2 = JSON.deserialize
p2.say
# should output 'bob'
I tried as_json (from ActiveSupport I guess), but result is {'name': 'bob'} and obviously type information is lost and after deserialization I just have a hash, not a Person instance.
Ruby's JSON library supports the Marshal interface. Short answer: you need to define #to_json and self#json_create in your class.
The trick is that you need to store the name of the class you want to round-trip back to in the json itself; the default place to do this is as the value of the key json_class and there's likely no reason to change it.
Here's a ridiculously simple example:
require 'json'
class A
attr_accessor :a,:b
def initialize(a,b)
#a = a
#b = b
end
def to_json(*a)
{
"json_class" => self.class.name,
"data" => {:a => #a, :b=>#b}
}.to_json(*a)
end
def self.json_create(h)
self.new(h["data"]["a"], h["data"]["b"])
end
end
Then you can round-trip it with JSON.generate and JSON.load. Note that JSON.parse will not work; it'll just give you back the expected hash.
[29] pry(main)> x = A.new(1,2)
=> #<A:0x007fbda457efe0 #a=1, #b=2>
[30] pry(main)> y = A.new(3,4)
=> #<A:0x007fbda456ea78 #a=3, #b=4>
[31] pry(main)> str = JSON.generate(x)
=> "{\"json_class\":\"A\",\"data\":{\"a\":1,\"b\":2}}"
[32] pry(main)> z = JSON.load(str)
=> #<A:0x007fbda43fc050 #a=1, #b=2>
[33] pry(main)> arr = [x,y,z]
=> [#<A:0x007fbda457efe0 #a=1, #b=2>, #<A:0x007fbda456ea78 #a=3, #b=4>, #<A:0x007fbda43fc050 #a=1, #b=2>]
[34] pry(main)> str = JSON.generate(arr)
=> "[{\"json_class\":\"A\",\"data\":{\"a\":1,\"b\":2}},{\"json_class\":\"A\",\"data\":{\"a\":3,\"b\":4}},{\"json_class\":\"A\",\"data\":{\"a\":1,\"b\":2}}]"
[35] pry(main)> arr2 = JSON.load(str)
=> [#<A:0x007fbda4120a48 #a=1, #b=2>, #<A:0x007fbda4120700 #a=3, #b=4>, #<A:0x007fbda4120340 #a=1, #b=2>]

How to change Ruby hash default value to arbitrary object after it has been created?

This is my code:
[164] pry(main)> h = Hash.new "A"
=> {}
[165] pry(main)> h["x"]
=> "A"
[166] pry(main)> h["x"] = "XXX"
=> "XXX"
[167] pry(main)> h["x"]
=> "XXX"
[168] pry(main)> h["y"].downcase!
=> "a"
[169] pry(main)> h["y"]
=> "a"
[170] pry(main)> h["z"]
=> "a"
[171] pry(main)>
As you can see, I can create a hash h with a default value A. After h has been created, I can change this default value to its downcase!, which is a. Here my question is, how can I change it to arbitrary value, like "xyz". Apparently I can't use something like h["not-exist"] = "xyz";, as that will create the new key with the value.
You can use Hash#default= to set the default value.
h = Hash.new "A"
#=> {}
h.default = "xyz"
#=> "xyz"
h["non-exist"]
#=> "xyz"

Hash with array as key

I'm defining a hash with an array as a key and another array as its value. For example:
for_example = {[0,1] => [:a, :b, :c]}
Everything is as expected below.
my_hash = Hash.new([])
an_array_as_key = [4,2]
my_hash[an_array_as_key] #=> []
my_hash[an_array_as_key] << "the" #=> ["the"]
my_hash[an_array_as_key] << "universal" #=> ["the", "universal"]
my_hash[an_array_as_key] << "answer" #=> ["the", "universal", "answer"]
But if I try to access the keys:
my_hash #=> {}
my_hash.keys #=> []
my_hash.count #=> 0
my_hash.values #=> []
my_hash.fetch(an_array_as_key) # KeyError: key not found: [4, 2]
my_hash.has_key?(an_array_as_key) #=> false
Rehash doesn't help:
my_hash #=> {}
my_hash.rehash #=> {}
my_hash.keys #=> []
But the values are saved:
my_hash[an_array_as_key] #=> ["the", "universal", "answer"]
Am I missing something?
To understand this, You need to understand the difference between Hash::new and Hash::new(ob). Suppose you define a hash object using Hash::new or hash literal {}. Now whenever you will write a code hsh[any_key], there is two kind of output may be seen, if any_key don't exist, then default value nil will be returned,otherwise whatever value is associated with the key will be returned. The same explanation will be applicable if you create any Hash object using Hash.new.
Now Hash.new(ob) is same as Hash.new, with one difference is, you can set any default value you want, for non existent keys of that hash object.
my_hash = Hash.new([])
my_hash[2] # => []
my_hash[2].object_id # => 83664630
my_hash[4] # => []
my_hash[4].object_id # => 83664630
my_hash[3] << 4 # => [4]
my_hash[3] # => [4]
my_hash[3].object_id # => 83664630
my_hash[5] << 8 # => [4, 8]
my_hash[5] # => [4, 8]
my_hash[5].object_id # => 83664630
Now see in the above example my_hash has no keys like 2,3 and 4. But the object_id proved that, all key access results in to return the same array object. my_hash[2] is not adding the key to the hash my_hash, rather trying to access the value of the key 2 if that key exist, otherwise it is returning the default value of my_hash. Remember all lines like my_hash[2],my_hash[3] etc is nothing but a call to Hash#[] method.
But there is a third way to go, may be you are looking for, which is Hash::new {|hash, key| block }.With this style you can add key to the hash object if that key doesn't exist, with a default value of same class instance,but not the same instance., while you are doing actually Hash#[] method call.
my_hash = Hash.new { |hash, key| hash[key] = []}
my_hash[2] # => []
my_hash[2].object_id # => 76312700
my_hash[3] # => []
my_hash[3].object_id # => 76312060
my_hash.keys # => [2, 3]

Resources