Related
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!
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 can I convert a string of JSON data to a multidimensional array?
# Begin with JSON
json_data = "[
{"id":1,"name":"Don"},
{"id":2,"name":"Bob"},
...
]"
# do something here to convert the JSON data to array of arrays.
# End with multidimensional arrays
array_data = [
["id", "name"],
[1,"Don"],
[2,"Bob"],
...
]
For readability and efficiency, I would do it like this:
require 'json'
json_data = '[{"id":1,"name":"Don"},{"id":2,"name":"Bob"}]'
arr = JSON.parse(json_data)
#=> "[{\"id\":1,\"name\":\"Don\"},{\"id\":2,\"name\":\"Bob\"}]"
keys = arr.first.keys
#=> ["id", "name"]
arr.map! { |h| h.values_at(*keys) }.unshift(keys)
#=> [["id", "name"], [1, "Don"], [2, "Bob"]]
This should do the trick:
require 'json'
json_data = '[{"id":1,"name":"Don"},{"id":2,"name":"Bob"}]'
JSON.parse(json_data).inject([]) { |result, e| result + [e.keys, e.values] }.uniq
First, we read the JSON into an array with JSON.parse. For each element in the JSON, we collect all keys and values using inject which results in the following array:
[
["id", "name"],
[1, "Don"],
["id", "name"],
[2, "Bob"]
]
To get rid of the repeating key-arrays, we call uniq and are done.
[
["id", "name"],
[1, "Don"],
[2, "Bob"]
]
Adding to #tessi's answer, we can avoid using 'uniq' if we combine 'with_index' and 'inject'.
require 'json'
json_data = '[{"id":1,"name":"Don"},{"id":2,"name":"Bob"}]'
array_data = JSON.parse(json_data).each.with_index.inject([]) { |result, (e, i)| result + (i == 0 ? [e.keys, e.values] : [e.values]) }
puts array_data.inspect
The result is:
[["id", "name"], [1, "Don"], [2, "Bob"]]
I am comparing large arrays to find missing elements. But one array will all be capitalized and the other is not. So I want to format the one array but am having issues. This is an example of the array I am trying to format.
array = [ 023, "Bac001", "abc123"]
Then I try to capitalize everything
array.map!(&:upcase)
but get undefined method 'upcase' for 023
is there a way around this?
I'd use Object#respond_to?:
def upcase_elements(ary)
ary.map { |e| e.respond_to?(:upcase) ? e.upcase : e }
end
upcase_elements([23, "BAC001", "ABC123"])
#=> [23, "BAC001", "ABC123"]
Checking if the receiver responds to a method is more flexible than checking its type:
upcase_elements([:foo, "bar"])
#=> [:FOO, "BAR"]
array.map! { |s| s.kind_of?(String) ? s.upcase : s }
This will not attempt to upcase any non-string element of the array. So it will work on arrays like:
array = [23, 1.27, "Bac001", "abc123", {:foo => 3}]
Yielding:
[23, 1.27, "BAC001", "ABC123", {:foo => 3}]
I am trying to pass an array to a ruby script from a command line and facing some issue.
Here is the problem:
require 'pp'
def foo(arr1, var, arr2, var2)
puts arr1.class
pp arr1
pp arr1[0]
puts arr2.class
pp arr2
pp arr2[0]
end
foo [1, 2], 3, [5, 6], 8
Here is the output:
Array
[1, 2]
1
Array
[5, 6]
5
All is fine so far. Now I change my script to accept argument from the command line:
require 'pp'
def foo(arr1,var)
puts arr1.class
pp arr1
pp arr1[0]
end
foo ARGV[0],3
Here is the output:
jruby test.rb [1, 2], 3, [5, 6], 8
String
"[1,"
91
String
"2],"
50
As you can see, the array gets passed as a string and arr[0] basically prints the ascii value.
So the question is how do I pass an array from the command line , hopefully in one line.
Also I believe this question is related to all shell invocations than just ruby ?
I am using bash shell.
Update:
Just updated the question to indicate that there can be multiple arrays at different positions
Here's a list of ways to accomplish this. Stay away from the eval-based solutions. My favorite (though I don't know ruby, but this is my favorite:
irb(main):001:0> s = "[5,3,46,6,5]"
=> "[5,3,46,6,5]"
irb(main):002:0> a = s.scan( /\d+/ )
=> ["5", "3", "46", "6", "5"]
irb(main):003:0> a.map!{ |s| s.to_i }
=> [5, 3, 46, 6, 5]
The arguments will always come in as string, you need to find a way to turn them into the format you want, in your example an array of values, followed by a single value. I suggest using trollop for this, to take the heavy lifting out of dealing with the arguments. It can accept multi-value arguments, e.g.
require 'trollop'
opts = Trollop.options do
opt :array, 'an array', type: :ints
opt :val, 'a value', type: :int
end
puts "array: #{opts[:array].inspect}"
puts "val: #{opts[:val].inspect}"
Then you can do:
$ ruby test.rb -a 1 2 -v 3
array: [1, 2]
val: 3
And extra nice:
$ ruby test.rb --help
Options:
--array, -a <i+>: an array
--val, -v <i>: a value
--help, -h: Show this message
You can use eval although you might open a security hole:
require 'pp'
def foo(arr1, var, arr2, var2)
puts arr1.class
pp arr1
pp arr1[0]
puts arr2.class
pp arr2
pp arr2[0]
end
eval("foo " + ARGV.join(" "))
Result
Array
[1, 2]
1
Array
[5, 6]
5
Hope it helps