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>]
Related
Read a csv format file and construct a new class with the name of the file dynamically. So if the csv is persons.csv, the ruby class should be person, if it's places.csv, the ruby class should be places
Also create methods for reading and displaying each value in "csv" file and values in first row of csv file will act as name of the function.
Construct an array of objects and associate each object with the row of a csv file.
For example the content of the csv file could be
name,age,city
Chris,23,NYC
Matt,23,SFO
I have found out a solution to this problem and code works efficiently for this, I am only facing issue while updating new key values being accessed. I also want to update values like :
p k.call 'name'
p k.call 'age'
p k.call 'city'
k.name = 'XYZ' # updating new key values
k.age = 25 # updating new key values
p k.call 'name'
p k.call 'age'
I am getting error at runtime :
undefined method `name' for #<Input:0x00007fb6c20d75c0>
Can someone please suggest, what is the correct way to implement getter setter method in this situation ?
require 'csv'
class ReadCsv
attr_accessor :arr
def initialize(source_name)
#klass = Class.new
class_name = File.basename(source_name, ".csv").capitalize
Object.const_set(class_name, #klass)
csv_read(source_name)
method_def
end
def csv_read(source_name)
#arr = CSV.read(source_name).transpose
end
def method_def
#klass_obj = #klass.new
arr.each { |method_name, *a| #klass_obj.define_singleton_method(method_name.to_s) { a } }
end
def universal_attr_accessor(entity, attr_name) #change added
definition_method_name = "define_#{entity.is_a?(Object) ? 'singleton_' : ''}method".to_sym
entity.send(definition_method_name, "#{attr_name}=".to_sym) do |value|
instance_variable_set("##{attr_name}", value)
end
entity.send(definition_method_name, attr_name.to_sym) do
instance_variable_get("##{attr_name}")
end
end
def call(method_name)
#klass_obj.send(method_name)
universal_attr_accessor(#klass_obj, method_name)
end
end
k = ReadCsv.new('Input.csv')
p k.call 'name'
p k.call 'age'
p k.call 'city' . # until here code works just fine
k.name = 'XYZ' # I am not able to assign new key value for this dynamic method called at runtime.
k.age = 25
p k.call 'name'
p k.call 'age'
Expected Result :
k = ReadCsv.new('Input.csv')
p k.call 'name' # ['Chris','Matt']
p k.call 'age' # [23,23]
p k.call 'city' . # ['NYC','SFO']
k.name = 'XYZ'
k.age = 25
p k.call 'name' # ['Chris','Matt','XYZ']
p k.call 'age' # [23,23,25]
Now that I have a bit of time, here's the solution with a custom class, as opposed to OpenStruct:
require 'csv'
module CSVReader
def self.read(file_name)
class_name = File.basename(file_name, ".csv").capitalize
rows = CSV.read(file_name)
the_class = Struct.new(*rows.shift.map(&:to_sym))
Object.const_set(class_name, the_class)
rows.map { |row| the_class.new(*row) }
end
end
array = CSVReader.read("person.csv")
p array
# => [#<struct Person name="Bob", department="Engineering", salary="1000">, #<struct Person name="Jane", department="Sales", salary="2000">, #<struct Person name="John", department="Management", salary="5000">]
bob = array[0]
p bob
# => #<struct Person name="Bob", department="Engineering", salary="1000">
bob.department = "Inhuman Resources"
p bob.department
# => "Inhuman Resources"
p bob
# => #<struct Person name="Bob", department="Inhuman Resources", salary="1000">
As per comment from #Amadan, here is a barebone solution of what you are trying to achieve. It uses OpenStruct instead of custom classes.
persons =
CSV.parse(<<~ROWS, headers: true, header_converters: :symbol)
name,department,salary
Bob,Engineering,1000
Jane,Sales,2000
John,Management,5000
ROWS
persons =
persons.map(&:to_h).map(&OpenStruct.method(:new))
Resulting in:
#⇒ [#<OpenStruct name="Bob", department="Engineering", salary="1000">,
# #<OpenStruct name="Jane", department="Sales", salary="2000">,
# #<OpenStruct name="John", department="Management", salary="5000">]
And:
persons.first.name
#⇒ "Bob"
persons.first.name = "Mary"
#⇒ "Mary"
persons.first.name
#⇒ "Mary"
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.
I'm making a subclass of hash, which I want to be able to populate initially using a hash, i.e.:
class HashSub < Hash
def initialize(old_hash)
...
end
end
a = HashSub.new({'akey' => 'avalue'})
puts a['akey']
>> avalue
Since Hash.new doesn't take a hash, what's the cleanest way to achieve this?
The cleanest, in my experience, is to leave the initializer alone and to rely the class' [] operator:
>> class SubHash < Hash; end
=> nil
>> a = Hash[{:a => :b}]
=> {:a=>:b}
>> a.class
=> Hash
>> b = SubHash[{:a => :b}]
=> {:a=>:b}
>> b.class
=> SubHash
To improve on Denis' answer, you can alias the class method [] to new.
class SubHash < Hash; end
singleton_class{alias :new :[]}
end
SubHash.new(a: :b).class # => SubHash
H = Class.new Hash
a = {a: 2, b: 3}
b = H[ a ]
b.class #=> H
I have an array of hashes
Eg:
cars = [{:company => "Ford", :type => "SUV"},
{:company => "Honda", :type => "Sedan"},
{:company => "Toyota", :type => "Sedan"}]
# i want to fetch all the companies of the cars
cars.collect{|c| c[:company]}
# => ["Ford", "Honda", "Toyota"]
# i'm lazy and i want to do something like this
cars.collect(&:company)
# => undefined method `company'
I was wondering if there is a similar shortcut to perform the above.
I believe your current code cars.collect{|c| c[:company]} is the best way if you're enumerating over an arbitrary array. The method you would pass in via the & shortcut would have to be a method defined on Hash since each object in the array is of type Hash. Since there is no company method defined for Hash you get the "undefined method 'company'" error.
You could use cars.collect(&:company) if you were operating on an Array of Cars though, because each object passed into the collect block would be of type Car (which has the company method available). So maybe you could modify your code so that you use an array of Cars instead.
You could convert the hashes to OpenStructs.
require 'ostruct'
cars = [{:company => "Ford", :type => "SUV"},
{:company => "Honda", :type => "Sedan"},
{:company => "Toyota", :type => "Sedan"}]
cars = cars.map{|car| OpenStruct.new(car)}
p cars.map( &:company )
#=> ["Ford", "Honda", "Toyota"]
It's impossible to use in your case, because in collect you use method [] and argument :company. The construction &:company takes labels :company and converts to Proc, so it's only one argument - the name of method.
Unfortunately Ruby hashes can't do that. Clojure maps on the other hand have functions for each key which return the corresponding value, which would be easy enough to do if you are so inclined (you should also add the corresponding respond_to? method):
>> class Hash
.. def method_missing(m)
.. self.has_key?(m) ? self[m] : super
.. end
.. end #=> nil
>> cars.collect(&:company) #=> ["Ford", "Honda", "Toyota"]
>> cars.collect(&:compay)
NoMethodError: undefined method `compay' for {:type=>"SUV", :company=>"Ford"}:Hash
Note: I'm not advising this, I'm just saying it's possible.
Another horrible monkeypatch you shouldn't really use:
class Symbol
def to_proc
if self.to_s =~ /bracket_(.*)/
Proc.new {|x| x[$1.to_sym]}
else
Proc.new {|x| x.send(self)}
end
end
end
cars = [{:company => "Ford", :type => "SUV"},
{:company => "Honda", :type => "Sedan"},
{:company => "Toyota", :type => "Sedan"}]
cars.collect(&:bracket_company)
Consider a "person" stored in a hash. Two examples are:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
If the "person" doesn't have any children, the "children" element is not present. So, for Mr. Slate, we can check whether he has parents:
slate_has_children = !slate[:person][:children].nil?
So, what if we don't know that "slate" is a "person" hash? Consider:
dino = {:pet => {:name => "Dino"}}
We can't easily check for children any longer:
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
So, how would you check the structure of a hash, especially if it is nested deeply (even deeper than the examples provided here)? Maybe a better question is: What's the "Ruby way" to do this?
The most obvious way to do this is to simply check each step of the way:
has_children = slate[:person] && slate[:person][:children]
Use of .nil? is really only required when you use false as a placeholder value, and in practice this is rare. Generally you can simply test it exists.
Update: If you're using Ruby 2.3 or later there's a built-in dig method that does what's described in this answer.
If not, you can also define your own Hash "dig" method which can simplify this substantially:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
This method will check each step of the way and avoid tripping up on calls to nil. For shallow structures the utility is somewhat limited, but for deeply nested structures I find it's invaluable:
has_children = slate.dig(:person, :children)
You might also make this more robust, for example, testing if the :children entry is actually populated:
children = slate.dig(:person, :children)
has_children = children && !children.empty?
With Ruby 2.3 we'll have support for the safe navigation operator:
https://www.ruby-lang.org/en/news/2015/11/11/ruby-2-3-0-preview1-released/
has_children now could be written as:
has_children = slate[:person]&.[](:children)
dig is being added as well:
has_children = slate.dig(:person, :children)
Another alternative:
dino.fetch(:person, {})[:children]
You can use the andand gem:
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
You can find further explanations at http://andand.rubyforge.org/.
One could use hash with default value of {} - empty hash. For example,
dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false
That works with already created Hash as well:
dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false
Or you can define [] method for nil class
class NilClass
def [](* args)
nil
end
end
nil[:a] #=> nil
Traditionally, you really had to do something like this:
structure[:a] && structure[:a][:b]
However, Ruby 2.3 added a feature that makes this way more graceful:
structure.dig :a, :b # nil if it misses anywhere along the way
There is a gem called ruby_dig that will back-patch this for you.
def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}_#{h_k}"] = h_v
end
else
h[k] = v
end
end
end
irb(main):012:0> fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
=> {:person=>{:name=>"Fred", :spouse=>"Wilma", :children=>{:child=>{:name=>"Pebbles"}}}}
irb(main):013:0> slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
irb(main):014:0> flatten_hash(fred).keys.any? { |k| k.include?("children") }
=> true
irb(main):015:0> flatten_hash(slate).keys.any? { |k| k.include?("children") }
=> false
This will flatten all the hashes into one and then any? returns true if any key matching the substring "children" exist.
This might also help.
dino_has_children = !dino.fetch(person, {})[:children].nil?
Note that in rails you can also do:
dino_has_children = !dino[person].try(:[], :children).nil? #
Here is a way you can do a deep check for any falsy values in the hash and any nested hashes without monkey patching the Ruby Hash class (PLEASE don't monkey patch on the Ruby classes, such is something you should not do, EVER).
(Assuming Rails, although you could easily modify this to work outside of Rails)
def deep_all_present?(hash)
fail ArgumentError, 'deep_all_present? only accepts Hashes' unless hash.is_a? Hash
hash.each do |key, value|
return false if key.blank? || value.blank?
return deep_all_present?(value) if value.is_a? Hash
end
true
end
Simplifying the above answers here:
Create a Recursive Hash method whose value cannot be nil, like as follows.
def recursive_hash
Hash.new {|key, value| key[value] = recursive_hash}
end
> slate = recursive_hash
> slate[:person][:name] = "Mr. Slate"
> slate[:person][:spouse] = "Mrs. Slate"
> slate
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
slate[:person][:state][:city]
=> {}
If you don't mind creating empty hashes if the value does not exists for the key :)
You can try to play with
dino.default = {}
Or for example:
empty_hash = {}
empty_hash.default = empty_hash
dino.default = empty_hash
That way you can call
empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
Given
x = {:a => {:b => 'c'}}
y = {}
you could check x and y like this:
(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil
Thks #tadman for the answer.
For those who want perfs (and are stuck with ruby < 2.3), this method is 2.5x faster:
unless Hash.method_defined? :dig
class Hash
def dig(*path)
val, index, len = self, 0, path.length
index += 1 while(index < len && val = val[path[index]])
val
end
end
end
and if you use RubyInline, this method is 16x faster:
unless Hash.method_defined? :dig
require 'inline'
class Hash
inline do |builder|
builder.c_raw '
VALUE dig(int argc, VALUE *argv, VALUE self) {
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
self = rb_hash_aref(self, *argv);
if (NIL_P(self) || !--argc) return self;
++argv;
return dig(argc, argv, self);
}'
end
end
end
You can also define a module to alias the brackets methods and use the Ruby syntax to read/write nested elements.
UPDATE: Instead of overriding the bracket accessors, request Hash instance to extend the module.
module Nesty
def []=(*keys,value)
key = keys.pop
if keys.empty?
super(key, value)
else
if self[*keys].is_a? Hash
self[*keys][key] = value
else
self[*keys] = { key => value}
end
end
end
def [](*keys)
self.dig(*keys)
end
end
class Hash
def nesty
self.extend Nesty
self
end
end
Then you can do:
irb> a = {}.nesty
=> {}
irb> a[:a, :b, :c] = "value"
=> "value"
irb> a
=> {:a=>{:b=>{:c=>"value"}}}
irb> a[:a,:b,:c]
=> "value"
irb> a[:a,:b]
=> {:c=>"value"}
irb> a[:a,:d] = "another value"
=> "another value"
irb> a
=> {:a=>{:b=>{:c=>"value"}, :d=>"another value"}}
I don't know how "Ruby" it is(!), but the KeyDial gem which I wrote lets you do this basically without changing your original syntax:
has_kids = !dino[:person][:children].nil?
becomes:
has_kids = !dino.dial[:person][:children].call.nil?
This uses some trickery to intermediate the key access calls. At call, it will try to dig the previous keys on dino, and if it hits an error (as it will), returns nil. nil? then of course returns true.
You can use a combination of & and key? it is O(1) compared to dig which is O(n) and this will make sure person is accessed without NoMethodError: undefined method `[]' for nil:NilClass
fred[:person]&.key?(:children) //=>true
slate[:person]&.key?(:children)