So I have defined this class File inside a module and what I want to do is rewrite the self.parse method so that it uses case. I'm new to Ruby so this is not straightforward for me. Also the method must contain in it's body no more than 8 lines of code. Any ideas how to do it? Also I asked it on Code Review and they said it was off topic for them.
module RBFS
class File
attr_accessor :content
def initialize (data = nil)
#content = data
end
def data_type
case #content
when NilClass then :nil
when String then :string
when TrueClass , FalseClass then :boolean
when Float , Integer then :number
when Symbol then :symbol
end
end
def serialize
case #content
when NilClass then "nil:"
when String then "string:#{#content}"
when TrueClass , FalseClass then "boolean:#{#content}"
when Symbol then "symbol:#{#content}"
when Integer , Float then "number:#{#content}"
end
end
def self.parse (str)
arr = str.partition(':')
if arr[0] == "nil" then return File.new(nil) end
if arr[0] == "string" then return File.new(arr[2].to_s) end
if (arr[0] == "boolean" && arr[2].to_s == 'true') then return File.new(true) end
if (arr[0] == "boolean" && arr[2].to_s == 'false') then return File.new(false) end
if arr[0] == "symbol" then return File.new(arr[2].to_sym) end
return File.new(arr[2].to_i) if (arr[0] == "number" && arr[2].to_s.include?('.') == false)
return File.new(arr[2].to_f) if (arr[0] == "number" && arr[2].to_s.include?('.') == true)
end
end
end
Example how 'RBFS::File.parse' works:
RBFS::File.parse("string:"Hello world") => #<RBFS::File:0x1c45098 #content="Hello world"> #Tested in irb
I would personally prefer this:
def self.parse(arg)
key, value = arg.to_s.split(':')
{
'nil' => new(nil),
'string' => new(value),
'boolean' => new(value == 'true'),
'symbol' => new(value.to_sym),
'number' => new(value.include?('.') ? BigDecimal(value) : Integer(value))
}[key]
end
Code above is actually of 2 lines, broken into multiple lines for readability sake. However, if using case is a must then you can change your code to this:
def self.parse(arg)
key, value = arg.to_s.split(':')
case key
when 'nil' then new(nil)
when 'string' then new(value)
when 'boolean' then new(value == 'true')
when 'symbol' then new(value.to_sym)
when 'number' then new(value.include?('.') ? BigDecimal(value) : Integer(value))
end
end
In Ruby, case statements test using the case equality method #===. #=== returns true for several different of comparisons beyond the type checking you've already implemented in #serialize and #data_type. For example:
Integer === 1 //=> true
Numeric === 1 //=> true
(1..10) === 1 //=> true
1 === 1 //=> true
With that knowledge, we can construct a #parse method as follows:
def parse(serialized)
type, content = serialized.split(':') # A neat trick that'll make things more readable.
case type
when 'nil'
# ...
when 'string'
# ...
when 'boolean'
# ...
when 'symbol'
# ...
when 'number'
# ...
else
raise "Invalid RBFS file."
end
end
I'm not sure that you'll be able to do this in 8 lines without compromising the file's readability or dropping the error handling step I added at the end (which I highly recommend), but to get close, you can use the when ... then ... syntax.
Below is a suggestion for one way you might write this. It is untested, of course. I made two assumptions:
what you refer to as a[2] is a string, so a[2].to_s is unnecessary.
if a[0] => "boolean", a[2] is 'true' or 'false'.
module RBFS
class File
attr_accessor :content
def initialize (data = nil)
#content = data
end
def data_type
class_to_sym[#content.class]
end
def serialize
return nil if #content.class == NilClass
"#{class_to_sym[#content.class].to_s}:#{#content}"
end
def self.parse (str)
type,_,val = str.partition(':')
File.new(type_to_arg(type, val))
end
private
def class_to_sym
{ NilClass=>:nil, String=>:string, TrueClass=>:boolean,
FalseClass=>:boolean, Numeric=>:number, Symbol=>:symbol }
end
def type_to_arg(type, val)
case type
when "nil" then nil
when "string" then val
when "boolean" then val == 'true'
when "symbol" then val.to_sym
when "numeric" then val[/\./] ? val.to_f : val.to_i
end
end
end
end
end
If you prefer, you could replace val[/\./] with val.include?('.').
You could alternatively use a hash to simulate a case statement in type_to_arg:
def type_to_arg(type, val)
{ "nil" =>nil,
"string" =>val,
"boolean"=>(val=='true'),
"symbol" =>val.to_sym,
"number" =>val[/\./] ? val.to_f : val.to_i
}[type]
end
Related
I have the following function:
def parse_var(var)
value = instance_variable_get(var)
puts(value)
puts(value.to_s)
value.is_a?(Numeric) ? value.to_s : "\"#{value}\""
end
Variables of certain form are converted into an array when they are interpolated. In the above function, when value equals (684) 029-6183 x01024, value.to_s comes out to be ["(684) 029-6183 x01024", nil]. The same thing also happens when I try "#{value}".
What is causing this?
Here's the context of the code in question:
Entity.rb (context of above code)
require 'securerandom'
# Entity.rb
class Entity
def initialize
generate_uuid
end
def to_cypher
first_char = self.class.name.chr.downcase
"MERGE (#{first_char}:#{self.class.name} {#{attrs_to_cypher.join(', ')}}) RETURN #{first_char};"
end
protected
def rand_bool
[true, false].sample
end
private
def attrs_to_cypher
self.instance_variables.map do |var|
"#{camelize(var.to_s[1..-1])}:#{parse_var(var)}"
end
end
def generate_uuid
#uuid = SecureRandom.uuid
end
def parse_var(var)
value = instance_variable_get(var)
puts(value)
puts(value.to_s)
value.is_a?(Numeric) ? value.to_s : "\"#{value}\""
end
def camelize(s)
(s == "uuid") ? "UUID" : s.downcase.split('_').map(&:capitalize).join
end
end
PhoneNumber.rb (where the value is coming from)
require 'faker'
require_relative 'Entity'
# PhoneNumber.rb
class PhoneNumber < Entity
def initialize(**opts)
super()
#type = opts[:type] || rand_bool ? "cell" : "home"
#number = opts[:number] || #type == "cell" ? Faker::PhoneNumber.cell_phone : Faker::PhoneNumber.phone_number,
#country_code = opts[:country_code] || nil
#area_code = opts[:area_code] || nil
end
end
The following line of code is causing a couple of issues
#number = opts[:number] || #type == "cell" ? Faker::PhoneNumber.cell_phone : Faker::PhoneNumber.phone_number,
First, the || operator has a higher precedence than the ? operator, so it actually looks like:
#number = (opts[:number] || #type == "cell") ? Faker::PhoneNumber.cell_phone : Faker::PhoneNumber.phone_number,
and you probably were wanting this:
#number = opts[:number] || (#type == "cell" ? Faker::PhoneNumber.cell_phone : Faker::PhoneNumber.phone_number),
As it stands, it doesn't matter what you pass into opts[:number], you'll always get a Faker::PhoneNumber assigned. (The line above, assigning type, looks like it would have this same precedence issue)
Second, you have a stray comma at the end of the line. This is turning the entire line into the first element of an array, and doesn't interfere with assigning the variable on the next line, so it's hard to catch:
opts = { number: '123' }
type = "cell"
number = opts[:number] || type == "cell" ? "truthy" : "falsey",
country = "some value"
p number # => ["truthy", "some value"]
p country # => "some value"
Your variable contains ["(684) 029-6183 x01024", nil], not a string. You rely on puts(value) output to determine it’s value which is plain wrong:
puts ["(684) 029-6183 x01024", nil]
#⇒ (684) 029-6183 x01024
#
puts(nil) returns an empty string, making you think the original value is a string.
To overcome this issue you might:
value = [*value].compact.join
"0".to_i == 0
also:
"abcdefg".to_i == 0
I want to make sure the string I'm parsing really is just a number (0 included).
Integer("0").zero? rescue false
# => true
Integer("1").zero? rescue false
# => false
Integer("abcdefg").zero? rescue false
# => false
def string_is_integer?(string)
!string.match(/^(\d)+$/).nil? # \d looks for digits
end
def string_is_float?(string)
!string.match(/^(\d)+\.(\d)+$/).nil?
end
def string_is_number?(string)
string_is_integer?(string) || string_is_float?(string)
end
Or, if you don't mind:
def string_is_number?(string)
begin
true if Float(string)
rescue ArgumentError
end || false
end
def is_zero?(string)
(Integer(string) rescue 1).zero?
end
is_zero? "0" #=> true
is_zero? "00000" #=> true
is_zero? "0cat" #=> false
is_zero? "1" #=> false
is_zero? "-0" #=> true
is_zero? "0_000" #=> true
is_zero? "0x00" #=> true
is_zero? "0b00000000" #=> true
Several of these examples illustrate why it's preferable to use Kernel#Integer rather than a regular expression.
First test if the string is integer or not and then match
def is_i?(x)
!!(x =~ /\A[-+]?[0-9]+\z/)
end
def is_zero?(x)
return is_i?(x) && x.to_i == 0
end
# is_zero?("0") will return true
# is_zero?("abcde") will return false
Or you can put these methods in String class like this
class String
def is_i?
!!(self =~ /\A[-+]?[0-9]+\z/)
end
def is_zero?
self.is_i? && self.to_i == 0
end
end
"0".is_zero? # true
"abcde".is_zero? # false
I want to make sure the string I'm parsing really is just a number.
There are two steps involved:
Parsing, i.e. converting the string into a number
Validation, i.e. checking if the string actually is a number
The built-in conversion functions like Integer and Float already perform both steps: they return a numeric result if the string is valid and raise an error otherwise.
Using these functions just for validation and discarding the return value is wasteful. Instead, use the return value and handle the exception, e.g.:
begin
puts 'Enter a number:'
number = Float(gets)
rescue
puts 'Not a valid number'
retry
end
# do something with number
or something like:
def parse_number(string)
Float(string) rescue nil
end
number = parse_number(some_string)
if number
# do something with number
else
# not a number
end
h = {
data: {
user: {
value: "John Doe"
}
}
}
To assign value to the nested hash, we can use
h[:data][:user][:value] = "Bob"
However if any part in the middle is missing, it will cause error.
Something like
h.dig(:data, :user, :value) = "Bob"
won't work, since there's no Hash#dig= available yet.
To safely assign value, we can do
h.dig(:data, :user)&.[]=(:value, "Bob") # or equivalently
h.dig(:data, :user)&.store(:value, "Bob")
But is there better way to do that?
It's not without its caveats (and doesn't work if you're receiving the hash from elsewhere), but a common solution is this:
hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }
hash[:data][:user][:value] = "Bob"
p hash
# => { :data => { :user => { :value => "Bob" } } }
And building on #rellampec's answer, ones that does not throw errors:
def dig_set(obj, keys, value)
key = keys.first
if keys.length == 1
obj[key] = value
else
obj[key] = {} unless obj[key]
dig_set(obj[key], keys.slice(1..-1), value)
end
end
obj = {d: 'hey'}
dig_set(obj, [:a, :b, :c], 'val')
obj #=> {d: 'hey', a: {b: {c: 'val'}}}
interesting one:
def dig_set(obj, keys, value)
if keys.length == 1
obj[keys.first] = value
else
dig_set(obj[keys.first], keys.slice(1..-1), value)
end
end
will raise an exception anyways if there's no [] or []= methods.
I found a simple solution to set the value of a nested hash, even if a parent key is missing, even if the hash already exists. Given:
x = { gojira: { guitar: { joe: 'charvel' } } }
Suppose you wanted to include mario's drum to result in:
x = { gojira: { guitar: { joe: 'charvel' }, drum: { mario: 'tama' } } }
I ended up monkey-patching Hash:
class Hash
# ensures nested hash from keys, and sets final key to value
# keys: Array of Symbol|String
# value: any
def nested_set(keys, value)
raise "DEBUG: nested_set keys must be an Array" unless keys.is_a?(Array)
final_key = keys.pop
return unless valid_key?(final_key)
position = self
for key in keys
return unless valid_key?(key)
position[key] = {} unless position[key].is_a?(Hash)
position = position[key]
end
position[final_key] = value
end
private
# returns true if key is valid
def valid_key?(key)
return true if key.is_a?(Symbol) || key.is_a?(String)
raise "DEBUG: nested_set invalid key: #{key} (#{key.class})"
end
end
usage:
x.nested_set([:instrument, :drum, :mario], 'tama')
usage for your example:
h.nested_set([:data, :user, :value], 'Bob')
any caveats i missed? any better way to write the code without sacrificing readability?
Searching for an answer to a similar question I developmentally stumbled upon an interface similar to #niels-kristian's answer, but wanted to also support a namespace definition parameter, like an xpath.
def deep_merge(memo, source)
# From: http://www.ruby-forum.com/topic/142809
# Author: Stefan Rusterholz
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
memo.merge!(source, &merger)
end
# Like Hash#dig, but for setting a value at an xpath
def bury(memo, xpath, value, delimiter=%r{\.})
xpath = xpath.split(delimiter) if xpath.respond_to?(:split)
xpath.map!{|x|x.to_s.to_sym}.push(value)
deep_merge(memo, xpath.reverse.inject { |memo, field| {field.to_sym => memo} })
end
Nested hashes are sort of like xpaths, and the opposite of dig is bury.
irb(main):014:0> memo = {:test=>"value"}
=> {:test=>"value"}
irb(main):015:0> bury(memo, 'test.this.long.path', 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}}}}
irb(main):016:0> bury(memo, [:test, 'this', 2, 4.0], 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):017:0> bury(memo, 'test.this.long.path.even.longer', 'value')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):018:0> bury(memo, 'test.this.long.other.even.longer', 'other')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}, :other=>{:even=>{:longer=>"other"}}}, :"2"=>{:"4.0"=>"value"}}}}
A more ruby-helper-like version of #niels-kristian answer
You can use it like:
a = {}
a.bury!([:a, :b], "foo")
a # => {:a => { :b => "foo" }}
class Hash
def bury!(keys, value)
key = keys.first
if keys.length == 1
self[key] = value
else
self[key] = {} unless self[key]
self[key].bury!(keys.slice(1..-1), value)
end
self
end
end
If a = false and b = 2 is there a concise way to accomplish this? Using just return a unless b returns 'nil' instead of '2'.
I have
def checkit
return a unless b
b
end
Will this statement call b twice?
A real life case for this is:
def current_user
#current_user ||= authenticate_user
end
def authenticate_user
head :unauthorized unless authenticate_from_cookie
end
def authenticate_from_cookie
.
if user && secure_compare(user.cookie, cookie)
return user
else
return nil
end
end
Try this:
( b == true ) ? a : false
where a is a value you need to return
I do not know why you have false stored in the variable a, so I omitted that. As I understand, you want to pass a value to the method checkit, which should return the value if its boolean value is true (which means everything except values nil and false), and otherwise return the value. In that case, just use this:
def checkit(value)
value || false
end
checkit(1) # => 1
checkit(false) # => false
checkit('value') # => "value"
checkit(nil) # => false
In Ruby, is it possible to identify whether an object o has a class C as its ancestor in the class hierarchy using any method?
I've given an example below where I use a hypothetical method has_super_class? to accomplish it. How should I do this in reality?
o = Array.new
o[0] = 0.5
o[1] = 1
o[2] = "This is good"
o[3] = Hash.new
o.each do |value|
if (value.has_super_class? Numeric)
puts "Number"
elsif (value.has_super_class? String)
puts "String"
else
puts "Useless"
end
end
Expected Output:
Number
Number
String
Useless
Try obj.kind_of?(Klassname):
1.kind_of?(Fixnum) => true
1.kind_of?(Numeric) => true
....
1.kind_of?(Kernel) => true
The kind_of? method has also an identical alternative is_a?.
If you want to check only whether an object is (direct) instance of a class, use obj.instance_of?:
1.instance_of?(Fixnum) => true
1.instance_of?(Numeric) => false
....
1.instance_of?(Kernel) => false
You can also list all ancestors of an object by calling the ancestors method on its class. For instance 1.class.ancestors gives you [Fixnum, Integer, Precision, Numeric, Comparable, Object, PP::ObjectMixin, Kernel].
Just use .is_a?
o = [0.5, 1, "This is good", {}]
o.each do |value|
if (value.is_a? Numeric)
puts "Number"
elsif (value.is_a? String)
puts "String"
else
puts "Useless"
end
end
# =>
Number
Number
String
Useless
o.class.ancestors
using that list, we can implement has_super_class? like this (as singletone method):
def o.has_super_class?(sc)
self.class.ancestors.include? sc
end
The rad way:
1.class.ancestors => [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject]
1.class <= Fixnum => true
1.class <= Numeric => true
1.class >= Numeric => false
1.class <= Array => nil
If you want to be fancy with it, you could do something like this:
is_a = Proc.new do |obj, ancestor|
a = {
-1 => "#{ancestor.name} is an ancestor of #{obj}",
0 => "#{obj} is a #{ancestor.name}",
nil => "#{obj} is not a #{ancestor.name}",
}
a[obj.class<=>ancestor]
end
is_a.call(1, Numeric) => "Numeric is an ancestor of 1"
is_a.call(1, Array) => "1 is not a Array"
is_a.call(1, Fixnum) => "1 is a Fixnum"