Accessing Ruby Array as my_array.new_field = "abc" - ruby

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.

Related

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

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.

How to make a custom proc in a class so I can do: params_hash.downcase_keys instead of downcase_keys(params_hash)?

I have a hash of key values and I want to downcase all of the Keys.
However I don't want to have to create an local variable, I would rather functionally do it.
NOT:
x = downcase_keys(params_hash)
BUT THIS:
params_hash.downcase_keys
How would do this in ruby?
I do not understand why you tagged this question as functional-programming it seems you are looking for a method to call on a Hash object.
Be aware that you may encounter problems doing so because duplicated keys are going to be overwritten.
h = {"a" => 1, "B" => 2}
# Singleton method on the object
def h.downcase_keys
temp = map {|k,v| [k.downcase, v]}.to_h
clear
merge! temp
end
h.downcase_keys()
p h # {"a"=>1, "b"=>2}
# Method available on all Hash objects
class Hash
def downcase_keys
temp = map {|k,v| [k.downcase, v]}.to_h
clear
merge! temp
end
end
h = {"a" => 1, "B" => 2}
h.downcase_keys()
p h # {"a"=>1, "b"=>2}
def downcase_keys(hash)
hash.downcase_keys
end
h = {"C" => 1, "B" => 2, "D" => 3}
downcase_keys(h)
p h # {"c"=>1, "b"=>2, "d"=>3}

Sorting/accessing through an array of nested hashes

I have an array of hashes with names and ages:
array = [ {"bill" => 12}, {"tom" => 13}, {"pat" => 14} ]
I realize that, by calling the first method, this happens:
array.first # => {"bill" => 12}
Without defining a class, I'd like to do:
array.first.name # => "bill"
How can I do that?
Doing:
def name
array[0].keys
end
will define a private method, which can't be called on the receiver.
You have:
h = array.first #=> {"bill" => 12}
h.class #=> Hash
so if you want to create a method first, such that:
h.name #=> "bill"
you must define name on the class of its receiver (h), which is Hash. #shivam has shown you how to that, but (as #AndrewMarshall points out in a comment) that it pollutes the class Hash. A better way is to use Refinements.
Refinements was an experimental addition to v2.0, then modified and made permanent in v2.1. It provides a way to avoid "monkey-patching" by providing "a way to extend a class locally".
We can do that as follows. First, refine Hash within a module M:
module M
refine Hash do
def name
keys.first
end
end
end
The refinement has no effect until the keyword using is invoked on the module:
h = {"bill" => 12}
puts h.name
#-> undefined method `name' for {"bill"=>12}:Hash (NoMethodError)
After activating the refinement we can invoke name on h:
using M
h.name
#-> bill
The refinement applies to the remainder of the file, but does not apply to to code in other files. Suppose we were to add the following to the present file:
class A
def greeting(h)
puts "Hello, #{h.name}"
end
end
Then:
A.new.greeting({"bill" => 12})
#-> Hello, bill
Since your Array array consists of Hashes, when you do array.first a Hash is returned {"bill" => 12}.
You can now define a method Hash#name that can be applied to array.first
(a Hash)
Here:
class Hash
def name
self.keys.first
end
end
array.first.name
# => "bill"
#to print all names
array.each{|a| puts a.name}
# bill
# tom
# pat
# or collect them in an Array
array.map(&:name)
# => ["bill", "tom", "pat"]

Struct returning different things from reader and hash accessor

I have a struct like this:
class Item < Struct.new(:url, :list)
def list
#list ||= Array.new
end
end
I found out today that the .list() and [:list] returns different things:
i = Item.new
#=> #<struct Item url=nil, list=nil>
i.list
#=> []
i[:list]
#=> nil
i.list << 1
#=> [1]
i.list += [2]
#=> [1, 2]
i.list
#=> [1]
i[:list]
#=> [1, 2]
Why is this and how can I write my struct to have default empty array properly?
Someone already answered the 'why' bit, so maybe if you still want to do this with Struct, then why not try this:
class Item < Struct.new(:url, :list)
def list
self[:list] ||= Array.new
end
end
This works because while #list is an instance variable of your making, the accessor that Struct provides it it's own. (:list). self[:list] lets you get at it.
i = Item.new # => #<struct Item url=nil, list=nil>
i.list # => []
i[:list] # => []
# Compare using OBJECT IDENTITY (is i.list internally the same thing as i[:list]?)
i[:list].equal? i.list # true
i.list << 1 # => [1]
i.list += [2] # => [1, 2]
i.list # => [1, 2]
i[:list] # => [1, 2]
Assuming you want the other advantages of Struct and need to stick with it, you could write your own initialize method:
class Item < Struct.new(:url, :list)
def initialize(url, list = nil)
self.url = url
self.list = Array(list)
end
end
Array() will ensure anything passed in will be put in an array if it's not one already, and will return an empty array ([]) if nil is the argument.
I think you should better use Dash instead of Struct. Look:
require 'hashie'
class Item < Hashie::Dash
property :url
property :list, default: []
end
i = Item.new # => #<Item list=[]>
i.list # => []
i[:list] # => []
i.list << 1 # => [1]
i.list += [2] # => [1, 2]
i.list # => [1, 2]
i[:list] # => [1, 2]
Sergio Tulentsev answered the how can I write my struct to have default empty array properly? part, so I'll write the Why is this? part.
I am missing information but with a structure, it is written ::new creates a new class, named by aString, containing accessor methods for the given symbols.
So you have a accessor for :list but it is still different from your #list attribute. This means, you can name #list as you want, it will not be affiliated to the :list of the struct.
You also override the symbol accessor the struct was previously providing with your def list; end
i.list << 1 # adding 1 to #list set to Array.new
#=> [1]
i.list += [2] # equals i.list = i.list + [2]
# i.list= is the `:list` setter method.
# i.list is the #list getter method.
# It equals :list = #list + [2]
#=> [1, 2]
i.list # #list
#=> [1]
i[:list] # :list
#=> [1, 2]

The confusing Ruby method returns value

I have Ruby code:
def test_111(hash)
n = nil
3.times do |c|
if n
n[c] = c
else
n = hash
end
end
end
a = {}
test_111(a)
p a
Why it print {1=>1, 2=>2}, not the {} ??
In the test_111 method, the hash and the a use the same memory?
How can the a value be changed in the test_111 method?
I can't understand
Hashes are passed by reference. So, when you change a method parameter (which is a Hash), you change the original hash.
To avoid this, you should clone the hash.
test_111(a.dup)
This will create a shallow copy (that is, it will not clone child hashes that you may have).
A little illustration of what shallow copy is:
def mutate hash
hash[:new] = 1
hash[:existing][:value] = 2
hash
end
h = {existing: {value: 1}}
mutate h # => {:existing=>{:value=>2}, :new=>1}
# new member added, existing member changed
h # => {:existing=>{:value=>2}, :new=>1}
h = {existing: {value: 1}}
mutate h.dup # => {:existing=>{:value=>2}, :new=>1}
# existing member changed, no new members
h # => {:existing=>{:value=>2}}
In ruby, just about every object is passed by reference. This means when you do something as simple as
a = b
unless a was one of the simple types, after this assignment a and b will point to the same thing.
This means if you alter the second variable, the first is affected the same way:
irb(main):001:0> x = "a string"
=> "a string"
irb(main):002:0> y = x
=> "a string"
irb(main):003:0> x[1,0] = "nother"
=> "nother"
irb(main):004:0> x
=> "another string"
irb(main):005:0> y
=> "another string"
irb(main):006:0>
and of course the same applies for hashes:
irb(main):006:0> a = { :a => 1 }
=> {:a=>1}
irb(main):007:0> b = a
=> {:a=>1}
irb(main):008:0> a[:b] = 2
=> 2
irb(main):009:0> a
=> {:a=>1, :b=>2}
irb(main):010:0> b
=> {:a=>1, :b=>2}
irb(main):011:0>
If you don't want this to happen, use .dup or .clone:
irb(main):001:0> a = "a string"
=> "a string"
irb(main):002:0> b = a.dup
=> "a string"
irb(main):003:0> a[1,0] = "nother"
=> "nother"
irb(main):004:0> a
=> "another string"
irb(main):005:0> b
=> "a string"
irb(main):006:0>
For most people dup and clone have the same effect.
So if you write a function that modifies one of its parameters, unless you specifically want those changes to be seen by the code that calls the function, you should first dup the parameter being modified:
def test_111(hash)
hash = hash.dup
# etc
end
The behavior of your code is called a side effect - a change to the program's state that isn't a core part of the function. Side effects are generally to be avoided.

Resources