Try create my own Struct via Ruby - ruby

I can't inherit the Struct. I must implement class which act like Struct.
Is there a way improve my code for use "ClassName" and functional like Struct ? And write k=Dave.new("Rachel" , "Greene") ???
class MyStruct
def self.new(*attributes)
puts "ppp"
dynamic_name = "ClassName"
Kernel.const_set(dynamic_name,Class.new() do
attributes.each do |action|
self.send(:define_method, action) {
puts "call #{action}"
}
end
end
)
end
end
# class ClassName
# def new *args
# puts "iii"
# end
# end
Dave = MyStruct.new(:name, :surname)
k=Dave.new() # k=Dave.new("Rachel" , "Greene")
k.surname
k.name

Here is a version of your code which works:
class MyStruct
def self.new(*attributes)
Class.new do
self.send(:attr_accessor, *attributes)
self.send(:define_method, :initialize) do |*values|
values.each_with_index { |val, i| self.send("#{attributes[i]}=", val) }
end
end
end
end
Dave = MyStruct.new(:name, :surname)
k = Dave.new('Rachel', 'Green')
# => #<Dave:0x00000001af2b10 #name="Rachel", #surname="Green">
k.name
# => "Rachel"
k.surname
# => "Green"
You don't need to const_set inside the method - Dave = is enough
I'm creating here an attr_accessor for each of the attributes, so you are getting a getter and a setter for each
In the initialize method I'm sending each value to its corresponding setter, to set all values. If there are less values than anticipated, the last attributes will not be set, if there are more - an exception will be thrown (undefined method '=')

Have you looked at the Struct class in Ruby?
http://www.ruby-doc.org/core-2.1.2/Struct.html
class MyStruct < Struct.new(:first_name, :last_name)
end
MyClassObj = MyStruct.new("Gavin", "Morrice")
Also, you shouldn't ever overwrite self.new, define initialize instead

Related

Dual-purpose accessor code refactor using define_method

To achieve a DSL like attribute assignment,a dual-purpose accessor was utilized. However, I was seeking a way to refactor the obvious code duplication.
class Layer
def size(size=nil)
return #size unless size
#size = size
end
def type(type=nil)
return #type unless type
#type = type
end
def color(color=nil)
return #color unless color
#color = color
end
end
I was thinking define those method in a class method by using define_method along with other methods to get/set the instance variables. However, the dilemma is how can I access the instance from class method?
def self.createAttrMethods
[:size,:type,:color].each do |attr|
define_method(attr) do |arg=nil|
#either use instance.send() or
#instance_variable_get/set
#But those method are instance method !!
end
end
end
Inside of define_method block, self will be pointing to current instance of class. So use instance_variable_get.
class Foo
def self.createAttrMethods
[:size,:type,:color].each do |attr|
define_method(attr) do |arg = nil|
name = "##{attr}"
return instance_variable_get(name) unless arg
instance_variable_set(name, arg)
end
end
end
createAttrMethods
end
f = Foo.new
f.size # => nil
f.size 3
f.size # => 3

Dynamic object creation and const_set

I'm trying to create enum - like java analog. I've this module.
module Enum
def self.included(base)
super
base.extend(Enum)
base.private_class_method :new
end
def enum(key, fields = {}, &class_body)
value = Class.new(self) do
fields.each_with_index do |item, i|
define_singleton_method(item.first.to_s.downcase) { item.last }
define_singleton_method("order") { i }
end
end
self.const_set key, value
end
end
and
class TestEnum
include Enum
enum :TEST, value: 1
end
When I tried to
>> TestEnum::TEST.is_a? TestEnum
false <-
I've got this. Why? I expected to see 'true'.
But
>> TestEnum::TEST.ancestors
TestEnum::TEST.ancestors
[TestEnum::TEST, TestEnum, Enum, Object
What I'm doing wrong?
obj.is_a? C is true if C is the class of obj, or a module included by obj's class, or one of the superclasses of the class of obj. It is used on instances, not subclasses.
class A; end
class B < A; end
b = B.new
b.is_a? A # => true
If you want to know if a class is a subclass of another given class, simply use ancestors.include?.

Ruby convert Object to Hash

Let's say I have a Gift object with #name = "book" & #price = 15.95. What's the best way to convert that to the Hash {name: "book", price: 15.95} in Ruby, not Rails (although feel free to give the Rails answer too)?
Just say (current object) .attributes
.attributes returns a hash of any object. And it's much cleaner too.
class Gift
def initialize
#name = "book"
#price = 15.95
end
end
gift = Gift.new
hash = {}
gift.instance_variables.each {|var| hash[var.to_s.delete("#")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}
Alternatively with each_with_object:
gift = Gift.new
hash = gift.instance_variables.each_with_object({}) { |var, hash| hash[var.to_s.delete("#")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}
Implement #to_hash?
class Gift
def to_hash
hash = {}
instance_variables.each { |var| hash[var.to_s.delete('#')] = instance_variable_get(var) }
hash
end
end
h = Gift.new("Book", 19).to_hash
Gift.new.instance_values # => {"name"=>"book", "price"=>15.95}
You can use as_json method. It'll convert your object into hash.
But, that hash will come as a value to the name of that object as a key. In your case,
{'gift' => {'name' => 'book', 'price' => 15.95 }}
If you need a hash that's stored in the object use as_json(root: false). I think by default root will be false. For more info refer official ruby guide
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json
For Active Record Objects
module ActiveRecordExtension
def to_hash
hash = {}; self.attributes.each { |k,v| hash[k] = v }
return hash
end
end
class Gift < ActiveRecord::Base
include ActiveRecordExtension
....
end
class Purchase < ActiveRecord::Base
include ActiveRecordExtension
....
end
and then just call
gift.to_hash()
purch.to_hash()
class Gift
def to_hash
instance_variables.map do |var|
[var[1..-1].to_sym, instance_variable_get(var)]
end.to_h
end
end
If you are not in an Rails environment (ie. don't have ActiveRecord available), this may be helpful:
JSON.parse( object.to_json )
You can write a very elegant solution using a functional style.
class Object
def hashify
Hash[instance_variables.map { |v| [v.to_s[1..-1].to_sym, instance_variable_get v] }]
end
end
Recursively convert your objects to hash using 'hashable' gem (https://rubygems.org/gems/hashable)
Example
class A
include Hashable
attr_accessor :blist
def initialize
#blist = [ B.new(1), { 'b' => B.new(2) } ]
end
end
class B
include Hashable
attr_accessor :id
def initialize(id); #id = id; end
end
a = A.new
a.to_dh # or a.to_deep_hash
# {:blist=>[{:id=>1}, {"b"=>{:id=>2}}]}
You should override the inspect method of your object to return the desired hash, or just implement a similar method without overriding the default object behaviour.
If you want to get fancier, you can iterate over an object's instance variables with object.instance_variables
Might want to try instance_values. That worked for me.
To plagiarize #Mr. L in a comment above, try #gift.attributes.to_options.
You can use symbolize_keys and in-case you have nested attributes we can use deep_symbolize_keys:
gift.as_json.symbolize_keys => {name: "book", price: 15.95}
Produces a shallow copy as a hash object of just the model attributes
my_hash_gift = gift.attributes.dup
Check the type of the resulting object
my_hash_gift.class
=> Hash
If you need nested objects to be converted as well.
# #fn to_hash obj {{{
# #brief Convert object to hash
#
# #return [Hash] Hash representing converted object
#
def to_hash obj
Hash[obj.instance_variables.map { |key|
variable = obj.instance_variable_get key
[key.to_s[1..-1].to_sym,
if variable.respond_to? <:some_method> then
hashify variable
else
variable
end
]
}]
end # }}}
Gift.new.attributes.symbolize_keys
To do this without Rails, a clean way is to store attributes on a constant.
class Gift
ATTRIBUTES = [:name, :price]
attr_accessor(*ATTRIBUTES)
end
And then, to convert an instance of Gift to a Hash, you can:
class Gift
...
def to_h
ATTRIBUTES.each_with_object({}) do |attribute_name, memo|
memo[attribute_name] = send(attribute_name)
end
end
end
This is a good way to do this because it will only include what you define on attr_accessor, and not every instance variable.
class Gift
ATTRIBUTES = [:name, :price]
attr_accessor(*ATTRIBUTES)
def create_random_instance_variable
#xyz = 123
end
def to_h
ATTRIBUTES.each_with_object({}) do |attribute_name, memo|
memo[attribute_name] = send(attribute_name)
end
end
end
g = Gift.new
g.name = "Foo"
g.price = 5.25
g.to_h
#=> {:name=>"Foo", :price=>5.25}
g.create_random_instance_variable
g.to_h
#=> {:name=>"Foo", :price=>5.25}
I started using structs to make easy to hash conversions.
Instead of using a bare struct I create my own class deriving from a hash this allows you to create your own functions and it documents the properties of a class.
require 'ostruct'
BaseGift = Struct.new(:name, :price)
class Gift < BaseGift
def initialize(name, price)
super(name, price)
end
# ... more user defined methods here.
end
g = Gift.new('pearls', 20)
g.to_h # returns: {:name=>"pearls", :price=>20}
Following Nate's answer which I haven't been able to compile:
Option 1
class Object
def to_hash
instance_variables.map{ |v| Hash[v.to_s.delete("#").to_sym, instance_variable_get(v)] }.inject(:merge)
end
end
And then you call it like that:
my_object.to_hash[:my_variable_name]
Option 2
class Object
def to_hash
instance_variables.map{ |v| Hash[v.to_s.delete("#"), instance_variable_get(v)] }.inject(:merge)
end
end
And then you call it like that:
my_object.to_hash["my_variable_name"]

How can one set property values when initializing an object in Ruby?

Given the following class:
class Test
attr_accessor :name
end
When I create the object, I want to do the following:
t = Test.new {name = 'Some Test Object'}
At the moment, it results in the name attribute still being nil.
Is that possible without adding an initializer?
ok,
I came up with a solution. It uses the initialize method but on the other hand do exactly what you want.
class Test
attr_accessor :name
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('#' + key.to_s, val)
end
end
def display
puts #name
end
end
t = Test.new :name => 'hello'
t.display
happy ? :)
Alternative solution using inheritance. Note, with this solution, you don't need to explicitly declare the attr_accessor!
class CSharpStyle
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('#' + key.to_s, val)
instance_eval "class << self; attr_accessor :#{key.to_s}; end"
end
end
end
class Test < CSharpStyle
def initialize(arg1, arg2, *init)
super(init.last)
end
end
t = Test.new 'a val 1', 'a val 2', {:left => 'gauche', :right => 'droite'}
puts "#{t.left} <=> #{t.right}"
As mentioned by others, the easiest way to do this would be to define an initialize method. If you don't want to do that, you could make your class inherit from Struct.
class Test < Struct.new(:name)
end
So now:
>> t = Test.new("Some Test Object")
=> #<struct Test name="Some Test Object">
>> t.name
=> "Some Test Object"
There is a general way of doing complex object initialization by
passing a block with necessary actions. This block is evaluated in the
context of the object to be initialized, so you have an easy access to
all instance variables and methods.
Continuing your example, we can define this generic initializer:
class Test
attr_accessor :name
def initialize(&block)
instance_eval(&block)
end
end
and then pass it the appropriate code block:
t = Test.new { #name = 'name' }
or
t = Test.new do
self.name = 'name'
# Any other initialization code, if needed.
end
Note that this approach does not require adding much complexity
to the initialize method, per se.
As previously mentioned, the sensible way to do this is either with a Struct or by defining an Test#initialize method. This is exactly what structs and constructors are for. Using an options hash corresponding to attributes is the closest equivalent of your C# example, and it's a normal-looking Ruby convention:
t = Test.new({:name => "something"})
t = Test.new(name: "something") # json-style or kwargs
But in your example you are doing something that looks more like variable assignment using = so let's try using a block instead of a hash. (You're also using Name which would be a constant in Ruby, we'll change that.)
t = Test.new { #name = "something" }
Cool, now let's make that actually work:
class BlockInit
def self.new(&block)
super.tap { |obj| obj.instance_eval &block }
end
end
class Test < BlockInit
attr_accessor :name
end
t = Test.new { #name = "something" }
# => #<Test:0x007f90d38bacc0 #name="something">
t.name
# => "something"
We've created a class with a constructor that accepts a block argument, which is executed within the newly-instantiated object.
Because you said you wanted to avoid using initialize, I'm instead overriding new and calling super to get the default behavior from Object#new. Normally we would define initialize instead, this approach isn't recommended except in meeting the specific request in your question.
When we pass a block into a subclass of BlockInit we can do more than just set variable... we're essentially just injecting code into the initialize method (which we're avoiding writing). If you also wanted an initialize method that does other stuff (as you mentioned in comments) you could add it to Test and not even have to call super (since our changes aren't in BlockInit#initialize, rather BlockInit.new)
Hope that's a creative solution to a very specific and intriguing request.
The code you're indicating is passing parameters into the initialize function. You will most definitely have to either use initialize, or use a more boring syntax:
test = Test.new
test.name = 'Some test object'
Would need to subclass Test (here shown with own method and initializer) e.g.:
class Test
attr_accessor :name, :some_var
def initialize some_var
#some_var = some_var
end
def some_function
"#{some_var} calculation by #{name}"
end
end
class SubClassedTest < Test
def initialize some_var, attrbs
attrbs.each_pair do |k,v|
instance_variable_set('#' + k.to_s, v)
end
super(some_var)
end
end
tester = SubClassedTest.new "some", name: "james"
puts tester.some_function
outputs: some calculation by james
You could do this.
class Test
def not_called_initialize(but_act_like_one)
but_act_like_one.each_pair do |variable,value|
instance_variable_set('#' + variable.to_s, value)
class << self
self
end.class_eval do
attr_accessor variable
end
end
end
end
(t = Test.new).not_called_initialize :name => "Ashish", :age => 33
puts t.name #=> Ashish
puts t.age #=> 33
One advantage is that you don't even have to define your instance variables upfront using attr_accessor. You could pass all the instance variables you need through not_called_initialize method and let it create them besides defining the getters and setters.
If you don't want to override initialize then you'll have to move up the chain and override new. Here's an example:
class Foo
attr_accessor :bar, :baz
def self.new(*args, &block)
allocate.tap do |instance|
if args.last.is_a?(Hash)
args.last.each_pair do |k,v|
instance.send "#{k}=", v
end
else
instance.send :initialize, *args
end
end
end
def initialize(*args)
puts "initialize called with #{args}"
end
end
If the last thing you pass in is a Hash it will bypass initialize and call the setters immediately. If you pass anything else in it will call initialize with those arguments.

self.class_eval <<DEF ... DEF

I'm trying to understand this function.
What I can see is an attribute and type are passed to the opal() method.
Then type_name takes its value from type as long as type is a Symbol or String. Otherwise, the name method is called on type. I imagine the name method is similar to the class method to get the class of the type argument.
After self.class_eval I'm kind of lost but my guess is this is defining maybe a block of code to be added to the class referenced by self.
How this works I'm not sure though.
Would appreciate if someone could explain what's going on after self.class_eval << DEF.
def opal(attr, type)
self.ds "#{attr}_id"
type_name = (type.is_a?(Symbol) || type.is_a?(String)) ? type : type.name
self.class_eval <<DEF
def #{attr}
if defined?(##{attr})
##{attr}
else
##{attr} = if self.#{attr}_id
#{type_name}.get(self.#{attr}_id)
else
nil
end
end
end
def #{attr}=(value)
self.#{attr}_id = value.key
##{attr} = value
end
DEF
end
Everything between <<DEF and DEF is just a string and the #{ ... }s work on that string like any other.
class_eval will cause the interpreter to run on the string in the context of the module.
So, if you know what attr and type are then you can work out what code is being run to add methods to the class.
Lets say attr is "foo" and type is "Bazzle". The code being run would be:
def foo
if defined?(#foo)
#foo
else
#foo = if self.foo_id
Bazzle.get(self.foo_id)
else
nil
end
end
end
def foo=(value)
self.foo_id = value.key
#foo = value
end
To make it easy to understand, let's suppose the value of 'attr' is 'foo', here's what it looks like now:
self.class_eval <<DEF
def foo
if defined?(#foo) # Return the value of attr if it's defined
#foo
else
#foo = if self.foo_id
#{type_name}.get(self.foo_id)
else
nil
end
end
end
def foo=(value) # Define setter
self.foo_id = value.key
#foo = value
end
DEF
So it's just defining some getter and setter methods for #foo, and evaluating it at the class level.

Resources