Ruby how can I save an instance of a class with JSON? - ruby

I have this code:
require 'json'
class A
attr_accessor :a, :b, :c
def initialize(a, b, c)
#a = a, #b = b, #c = c
end
end
a = A.new(1, "a", [1, 2, 3])
p a
puts "\n\nJSON: "
puts a.to_json
puts "\n\nJSON.pretty_generate: "
puts JSON.pretty_generate(a)
Output is really dissapointing:
#<A:0x000000019f4678 #b="a", #c=[1, 2, 3], #a=[1, "a", [1, 2, 3]]>
JSON:
"#<A:0x000000019f4678>"
JSON pretty generate:
blahblah.rb:285:in `generate': only generation of JSON objects
or arrays allowed (JSON::GeneratorError)
What's the difference between instance of class and JSON object?
How to convert instance of class to JSON object?

How to convert instance of class to JSON object?
Ruby's JSON implementation can serialize/deserialize custom objects, but you have to provide the details, i.e.
a to_json instance method that returns a JSON representation from your object and
a json_create class method that creates an object from its JSON representation
Here's an example using your class:
require 'json'
class A
attr_accessor :a, :b, :c
def initialize(a, b, c)
#a, #b, #c = a, b, c
end
def to_json(*args)
{
JSON.create_id => self.class.name,
'a' => a,
'b' => b,
'c' => c
}.to_json(*args)
end
def self.json_create(h)
new(h['a'], h['b'], h['c'])
end
end
a = A.new(1, 'a', [1, 2, 3])
#=> #<A:0x007f92cc8f37f0 #a=1, #b="a", #c=[1, 2, 3]>
a.to_json
#=> "{\"json_class\":\"A\",\"a\":1,\"b\":\"a\",\"c\":[1,2,3]}"
JSON.create_id defaults to the string "json_class". It's a special identifier that is recognizes by parse if you pass create_additions: true:
JSON.parse(a.to_json, create_additions: true)
#<A:0x007ff59c0f2578 #a=1, #b="a", #c=[1, 2, 3]>
Without this option, the parser doesn't invoke A.json_create and returns a plain hash instead:
JSON.parse(a.to_json)
#=> {"json_class"=>"A", "a"=>1, "b"=>"a", "c"=>[1, 2, 3]}
Calling JSON.pretty_generate(a) generates the following output:
{
"json_class": "A",
"a": 1,
"b": "a",
"c": [
1,
2,
3
]
}

One thing is a Ruby class:
From Wikipedia:
In object-oriented programming, a class is an extensible
program-code-template for creating objects, providing initial values
for state (member variables) and implementations of behavior (member
functions or methods).
and the other is a JSON object:
JSON: JavaScript Object Notation.
JSON is a syntax for storing and exchanging data.
JSON is an easier-to-use alternative to XML.
This is a well-known problem when serving data from the server to a web browser. You send a JSON representation of the data, normally for working in the server you parse this data to a Ruby object.
The error:
http://apidock.com/ruby/JSON/pretty_generate
You can only parse simple objects to JSON, you cannot parse a String. In order to this you can add the to_json method inside class A:
irb(main):046:0> JSON.pretty_generate("calimero")
JSON::GeneratorError: only generation of JSON objects or arrays allowed
from /Users/toni/.rvm/gems/ruby-2.2.3#stackoverflow/gems/json-1.8.3/lib/json/common.rb:285:in `generate'
from /Users/toni/.rvm/gems/ruby-2.2.3#stackoverflow/gems/json-1.8.3/lib/json/common.rb:285:in `pretty_generate'
from (irb):59
from /Users/toni/.rvm/rubies/ruby-2.2.3/bin/irb:11:in `<main>'
irb(main):060:0> JSON.pretty_generate([1,2,3])
=> "[\n 1,\n 2,\n 3\n]"
I recommend using the Virtus gem. It clarifies the type of the object are you going to parse to JSON, which for me is a good practice knowing what are you converting to JSON and what data are you expecting:
require 'virtus'
require 'json'
class A
include Virtus.model
attr_accessor :a, :b, :c
attribute :a, Integer
attribute :b, String
attribute :c, Array[Integer]
def initialize(a, b, c)
#a = a, #b = b, #c = c
end
end
irb(main):039:0> a = A.new(1, "a", [1, 2, 3])
=> #<A:0x007fc391882ac8 #b="a", #c=[1, 2, 3], #a=[1, "a", [1, 2, 3]]>
irb(main):040:0> a.attributes
=> {:a=>[1, "a", [1, 2, 3]], :b=>"a", :c=>[1, 2, 3]}
irb(main):053:0> require 'json'
=> true
irb(main):085:0> a.attributes.to_json
=> "{\"a\":[1,\"a\",[1,2,3]],\"b\":\"a\",\"c\":[1,2,3]}"
irb(main):096:0>

I would look at serializing the data instead, and make your class works so it can respond with the attributes rather easily.
require 'json'
class A
ATTRS = [:a, :b, :c]
attr_accessor *ATTRS
def initialize(params)
params.each do |k, v|
self.send("#{k}=".to_sym, v)
end
end
def attributes
values = {}
ATTRS.each do |key|
values[key] = self.send(key)
end
values
end
end
a = A.new(a: 1, b: "a", c: [1, 2, 3])
p a
data = a.attributes.to_json
puts data
b = A.new(JSON.parse(data))
p b
To explain this a bit, I'm using send to call the writable method for each attribute in the constructor, this goes through the params and sets the instance variable values.
The attributes method then uses the ATTRS constant array to build out an object map of just the properties of this instance. So we serialize that, and de-serialize to make a new instance.
This is pretty much how rails does it, though ActiveRecord uses an attributes hash for storing the properties over traditional accessors.

Related

How to pass arguments directy to a class without calling new method?

In Ruby a Hash can be created by:
Hash(a: 5, b: 6)
An Array can be created like this:
Array(100)
Sets can be created with the code:
require 'set'
Set[1,2,3]
So, how can I define a class that can accept arguments without calling the initialize method?
So, how can I define a class that can accept arguments without calling the initialize method?
You can't. In your examples, Hash and Array are actually methods.
And example with Set uses Set::[], naturally. And so it's not any different from any other class method that returns you instances of that class. For instance, User::create (or what-have-you).
In Ruby a Hash can be created by:
Hash(a: 5, b: 6)
Hash() is actually a method of the Kernel module:
p defined?(Hash()) # => "method"
p defined?(Kernel.Hash()) # => "method"
But without parentheses, Hash, Array, String, etc. all are just classes:
defined?(Hash) # => "constant"
defined?(Array) # => "constant"
In Ruby 2.6.3, the same goes for Arrays(), Complex(), Float(), Hash(), Integer(), Rational(), String(), and URI() - they all are methods.
But Set is a class:
require 'set'
p defined?(Set) # => "constant"
p set = Set[1,2,3] # => #<Set: {1, 2, 3}>
p set.to_a # => [1, 2, 3]
So, Set[1,2,3] is actually calling the [] method of Set. It looks kind of like this:
class Something
def initialize(*a)
#hash = {}
a.each { |v| #hash.store(v, nil) }
end
def self.[](*a) new(*a) end
define_method(:to_a) { #hash.keys }
define_method(:inspect) { "#<#{self.class}: {#{#hash.keys.to_s[1..-2]}}>" }
alias :to_s :inspect
end
p defined?(Something) # => "constant"
p set = Something[1,2,3] # => #<Something: {1, 2, 3}>
p set1 = Something[[1, 2, 3], 2, 2, 3, 4, {4 => :a}, 5] # => #<Something: {[1, 2, 3], 2, 3, 4, {4=>:a}, 5}>
p set.to_a # => [1, 2, 3]
p set1.to_a # => [[1, 2, 3], 2, 3, 4, [4, 4], 5]
Back to the question:
So, how can I define a class that can accept arguments without calling
the initialize method?
I don't think it's possible!

How to use &proc argument inside method

Array#max_by returns only a single value, but I want to have all values that have the max value.
hashes = [{a: 1, b:2}, {a:2, b:3}, {a:1, b:3}]
max = hashes.map{|h| h[:b]}.max
hashes.select{|h| h[:b] == max}
# => [{a: 2, b: 3}, {a: 1, b: 3}]
This code works fine, and I want to add it to Array class.
class Array
def max_values_by(&proc)
max = map(&proc).max
# I don't know how to use `select` here.
end
end
How to access the value of the &proc argument?
Use the proc in the block passed to select by calling it with call:
class Array
def max_values_by(&proc)
max = map(&proc).max
select { |h| proc.call(h) == max }
end
end
hashes.max_values_by { |h| h[:b] }
=> [{a: 2, b: 3}, {a: 1, b: 3}]
or with yield, which gives identical results:
def max_values_by(&proc)
max = map(&proc).max
select { |h| yield(h) == max }
end
Although proc.call is a little longer than yield, I prefer it in this case because it makes it clearer that the same block is being used in two places in the method, and because it's weird to use both the implicit block passing of yield and the explicit passing of &proc in the same method.
#DaveSchweisguth suggests a great implementation using select, like you requested. Another way of achieving the same result is by using group_by, like this:
>> hashes.group_by{|h| h[:b]}.max.last
=> [{:a=>2, :b=>3}, {:a=>1, :b=>3}]
or monkey-patched into Array as:
class Array
def max_values_by(&proc)
group_by(&proc).max.last
end
end

Mass method invocation

So in Ruby one can mass assign variables like this:
a, b, c = [1, 2, 3]
But what if I wanted to do the same for object methods, but without having to write out the whole thing like so:
foo.a, foo.b, foo.c = [1, 2, 3]
Is there a DRY way to accomplish this?
I am not sure you’ll like it, but the DRYest way I can think of is:
[:a, :b, :c].zip([1, 2, 3]).each { |k, v| foo.public_send "#{k}=", v }
# or vice versa
[1, 2, 3].zip([:a, :b, :c]).each { |v, k| foo.public_send "#{k}=", v }
Or, in more OO way:
class Foo
attr_accessor :a, :b, :c
def massive_assign attrs, values
attrs.zip(values).each { |k, v| public_send "#{k}=", v }
end
end
foo = Foo.new
foo.massive_assign([:a, :b, :c], [1, 2, 3])
Mass assignment to instance variables can be done like below as well:
foo.instance_eval { #a, #b, #c = [1, 2, 3] }
There's no specific syntax, but you could implement a setter for multiple attributes that are passed as a hash. Rails uses a similar approach:
class Foo
attr_accessor :a, :b, :c
def attributes=(attrs)
attrs.each do |name, value|
public_send("#{name}=", value)
end
end
end
foo = Foo.new
#=> #<Foo:0x007fc6d8a1e950>
foo.attributes = { a: 1, b: 2, c: 3 }
#=> #<Foo:0x007fc6d8a1e950 #a=1, #b=2, #c=3>
From the comments up there I assume that the poster is meaning a case where it's not actually foo but a more complex expression; and not only 3 assignments (method calls) but many.
There are 2 techniques that come to mind: tap and send:
some.complex[expression].which.evaluates.to.the.receiving.object.tap do |obj|
value_hash.each_pair do |key,value|
obj.send("#{key}=", value)
end
end
Hope that helps.
Of course, the usual security caveats apply (better make sure value_hash only contains valid/sane/allowed names).

Differences between [1,2,3].to_enum and [1,2,3].enum_for in Ruby

In Ruby I'm trying to understand between the to_enum and enum_for methods. Before I my question, I've provided some sample code and two examples to help w/ context.
Sample code:
# replicates group_by method on Array class
class Array
def group_by2(&input_block)
return self.enum_for(:group_by2) unless block_given?
hash = Hash.new {|h, k| h[k] = [] }
self.each { |e| hash[ input_block.call(e) ] << e }
hash
end
end
Example # 1:
irb (main)> puts [1,2,3].group_by2.inspect
=> #<Enumerator: [1, 2, 3]:group_by2>
In example #1: Calling group_by on the array [1,2,3], without passing in a block, returns an enumerator generated with the command self.enum_for(:group_by_2).
Example #2
irb (main)> puts [1,2,3].to_enum.inspect
=> #<Enumerator: [1, 2, 3]:each>
In example #2, the enumerator is generated by calling the to_enum method on the array [1,2,3]
Question:
Do the enumerators generates in examples 1 and 2, behave differently in any way? I can see from the inspected outputs that they show slightly different labels, but I can find any difference in the enumerators' behavior.
# Output for example #1
#<Enumerator: [1, 2, 3]:each> # label reads ":each"
# Output for example #2
#<Enumerator: [1, 2, 3]:group_by2> # label reads ":group_by2"
p [1, 2, 3].to_enum
p [1, 2, 3].enum_for
--output:--
#<Enumerator: [1, 2, 3]:each>
#<Enumerator: [1, 2, 3]:each>
From the docs:
to_enum
Creates a new Enumerator which will enumerate by calling method on
obj, passing args if any.
...
enum_for
Creates a new Enumerator which will enumerate by calling method on
obj, passing args if any.
ruby is a language that often has method names that are synonyms.
Followup question:
Does the symbol in the command [1,2,3].to_enum(:foo) serve a purpose,
other than replacing :each with :foo in the output?
Yes. By default, ruby hooks up the enumerator to the receiver's each() method. Some classes do not have an each() method, for instance String:
str = "hello\world"
e = str.to_enum
puts e.next
--output:--
1.rb:3:in `next': undefined method `each' for "helloworld":String (NoMethodError)
from 1.rb:3:in `<main>
to_enum() allows you to specify the method you would like the enumerator to use:
str = "hello\nworld"
e = str.to_enum(:each_line)
puts e.next
--output:--
hello
Now, suppose you have the array [1, 2, 3], and you want to to create an enumerator for your array. An array has an each() method, but instead of creating an enumerator with each(), which will return each of the elements in the array, then end; you want to create an enumerator that starts over from the beginning of the array once it reaches the end?
e = [1, 2, 3].to_enum(:cycle)
10.times do
puts e.next()
end
--output:--
1
2
3
1
2
3
1
2
3
1

Accessing Ruby Array as my_array.new_field = "abc"

I need to create a new empty object and access its attributes like my_object.title = 'abc' as opposed to my_object[:title] = 'abc'. How could I do this?
my_object = Array.new
my_object.title = "abc"
# => undefined method `title=' for []:Array
An Array in Ruby is not like an Array in JavaScript - you can't reference elements by name, but only by index. What you probably want is a Hash.
my_object = {}
my_object['title'] = "abc"
Or you can set it at initialization:
my_object = {'title' => 'abc'}
Alternatively, you can use OpenStruct to assign using dynamic attribute setters as you are doing:
my_object = OpenStruct.new
my_object.title = "abc"
It depends on what properties you want the object to have. You gave the example
my_object = Array.new
my_object.title = "abc"
If you want your objects to effectively be arrays, having access to all the methods available to arrays, but in addition you want to add additional properties, the way to do that is to create a subclass of Array:
class MyArray < Array
attr_accessor :title
def initialize(*args)
super
end
end
MyArray.ancestors # => [MyArray, Array, Enumerable, Object, Kernel, BasicObject]
a = MyArray.new(3,2) # => [2,2,2]
b = MyArray.new # => []
b << 4 << 5 << 6 # => [4,5,6]
e = a+b # => [2, 2, 2, 4, 5, 6]
e.class # => Array
a.title = "This is array a"
puts a.title # => "This is array a"
e.title = "me, e" # => NoMethodError: undefined method `title='
b.class # => MyArray
b.is_a? Array # => true
c = [7,8,9] # => [7, 8, 9]
c.is_a? MyArray # => false
d = a+c # => [2, 2, 2, 7, 8, 9]
super, in initialize, is what gives your class instances the properties of an array. When super is invoked within any method, it invokes the parent class method of the same name. So here it calls Array#initialize. Moreover, super passes along all the parameters its method received; that is, you don't need to write
super args
You can do this with most most Ruby objects (e.g., hashes and strings), but there are some exceptions. In particular, you cannot subclass Fixnum or Symbol.

Resources