Creating generic constructor in ruby - ruby

I found this interesting answer :
https://stackoverflow.com/a/2348854/169277
This is ok when you're trying to set instance variables it works really great.
Is there a way to apply the same logic or better one to create generic constructor like :
def initialize(obj)
obj.each do |k,v|
#find the setter for each k and set the value v to and return newly created object
end
end
If I had object TestObject:
class TestObject
attr_accessor :name, :surname, :sex
end
I was thinking to create it something like this:
TestObject.new({:name => 'Joe', :surname => 'Satriani'})
How would one achieve this?
So doing this would be a shorthand of :
t = TestObject.new
t.name = 'Joe'
t.surname = 'Satriani'

Sure, you can use send to send arbitrary messages to an object. Since we're operating on self here, we can just invoke send directly.
def initialize(obj)
obj.each do |k,v|
send(:"#{k}=", v)
end
end
For example, TestObject.new({:name => 'Joe'}) will call send "name=", "Joe".

You can inherit from Struct to make a simple object, and then pass in the attributes to the initializer:
class TestObject < Struct.new(:name, :surname, :sex)
end
TestObject.new('Joe', 'Satriani') #=> sex will be nil
You can use OpenStruct to make quick value objects with arbitrary attributes:
t = OpenStruct(name: 'Joe', surname: 'Satriani')
You can include a module like Virtus: https://github.com/solnic/virtus
Or you can do what Chris Heald said.

I think it would be better to use keyword arguments for this. After all, the Hash keys are guaranteed to be valid Ruby identifier Symbols since they need to match up with method names. You don't need the capability to pass in arbitrary Ruby objects as keys of the Hash.
def initialize(**attrs)
attrs.each do |attr, value| send(:"#{attr}=", value) end
end
TestObject.new(name: 'Joe', surname: 'Satriani')

Related

Reflection in ruby?

I am curious how this works. For example if I create a factory pattern based class where you can "register" classes for later use and then do something like
FactoryClass.register('YourClassName', [param, param, ...]);
FactoryClass.create('your_class_name').call_method_from_this_object
where 'class_name' is a key in a hash that maps to value: ClassName
is there anything like php reflection, where I can create an instance of a class based on a string name and pass in the arguments in? (in php the arguments would be an array of them that php then knows how what to do with)
So if we take a real world example:
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
# In the factory class, foo is then placed in a hash: {'foo' => 'Foo'}
# This step might not be required??
FactoryClass.create('Foo', ['hello'])
# Some where in your code:
FactoryClass.create('foo').get_something # => hello
Is this possible to do in ruby? I know everything is essentially an object, but I haven't seen any API or docs on creating class instances from string names like this and also passing in objects.
As for the hash above, thinking about it now I would probably have to do something like:
{'foo' => {'class' => 'Foo', 'params' => [param, param, ...]}}
This way when you call .create on the FactoryClass it would know, ok I can instantiate Foo with the associated params.
If I am way off base, please feel free to educate me.
Check out Module#const_get (retrieving a constant from a String) and Object#send (calling a method from a String).
Here is an answer that doesn't use eval.
PHP's Reflection is called Metaprogramming in Ruby, but they are quite different. Everything in Ruby is open and could be accessed.
Consider the following code:
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
#registered = { }
def register(reference_name, class_name, params=[])
#registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = #registered[reference_name]
Object.const_get(h[:class_name]).new(*(h[:params]))
end
register('foo', 'Foo', ['something'])
puts create('foo').get_something
You can use Object#const_get to get objects from strings. Object.const_get('Foo') will give you the object Foo.
However, you don't need to send class name as string. You can also pass around the class name as object and use that directly.
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
#registered = { }
def register(reference_name, class_name, params=[])
#registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = #registered[reference_name]
h[:class_name].new(*(h[:params]))
end
register('foo', Foo, ['something else'])
puts create('foo').get_something
Actually one of the strong points in ruby is meta-programming. So this is really easy to do in ruby.
I am going to skip the registering part, and jump straight to the creation
A simple implementation would be this
class FactoryClass
def self.create(class_name, params)
klass = Object.const_get(class_name)
klass.new(*params)
end
end
and then you can just do:
FactoryClass.create('YourClassName', [param, param, ...]);
and this would be equivalent to calling
YourClassName.new(param, param, ...)

Dynamically assigning attributes using strings as attribute setters in Ruby

I have a hash of name / value pairs:
attr_hash = {"attr1"=>"val1","attr2=>"val2"}
I want to cycle through each one of these values and assign them to an object like so:
thing = Thing.new
attr_hash.each do |k,v|
thing.k = v
end
class Thing
attr_accessor :attr1, :attr2
end
The problem of course being that attr1 is and attr2 are strings.. So I can't do something like thing."attr1"
I've tried doing:
thing.send(k,v) but that doesn't work
Use thing.send("#{k}=", v) instead.
You need to call the setter method, which for an attribute called name would be name=.
Following from your example:
attr_hash.each do |k,v|
thing.send("#{k}=", v)
end
Also, if this hash is coming from the user somehow, it might be a good idea to test if the setter exists before calling it, using respond_to?:
attr_hash.each do |k,v|
setter = "#{k}="
thing.send(setter, v) if thing.respond_to?(setter)
end
OpenStruct does it for you.
require 'ostruct'
attr_hash = {"attr1"=>"val1", "attr2"=>"val2"}
d = OpenStruct.new(attr_hash)
p d.attr1 #=> "val1"

How to metaprogramatically define typecasting attribute readers/writers in Ruby

I am trying to create a simple class that automatically converts a set of fields
to a specified Ruby type when the field is either set or read.
Here's what I have so far, and it works. However, it is not DRY and my
metaprogramming is rudimentary.
Is there a better, cleaner way to implement this?
class BasicModel
def self.fields(params)
params.each do |name, type|
# Define field writers
define_method("#{name}=") {|v| #fields[name] = v}
# Define field readers
case type.name
when 'String'
define_method(name) { #fields[name].to_s }
when 'Integer'
define_method(name) { #fields[name].to_i }
when 'Float'
define_method(name) { #fields[name].to_f }
else raise 'invalid field type'
end
end
end
fields(
name: String,
qty: Integer,
weight: Float
)
def initialize
#fields = {}
end
end
# specification
m = BasicModel.new
m.name => ""
m.name = 2 => 2
m.name => "2"
m.qty => 0
m.qty = "1" => "1"
m.qty => 1
m.weight => 0.0
m.weight = 10 => 10
m.weight => 10.0
What are the dis/advantages of typecasting on the reader vs. the writer?
For example, the following code typecasts on the writer, as opposed to the
reader (above). I also put the case inside the define_method.
class BasicModel
def self.fields(params)
params.each do |name, type|
define_method(name) { #fields[name] }
define_method("#{name}=") do |val|
#fields[name] = case type.name
when 'Integer' then val.to_i
when 'Float' then val.to_f
when 'String' then val.to_s
else raise 'invalid field type'
end
end
end
end
I was thinking that a possible concern is that decision trees (e.g. case
statement) should probably be kept out of the block of the define_method. I'm
assuming the statement is pointlessly evaluated each time the field is set/read.
Is this correct?
So, you asked two questions here:
How to metaprogramatically typecast
Whether to typecast on the reader or writer.
The second question is much easier to answer so let me start there:
I would cast on the writer. Why? While the difference is subtle, you have somewhat different behavior inside the object if you cast on the reader.
For example if you have a field, price of type Integer, and you cast this on read, then inside the class the value of price and #fields['price'] are not the same. This isn't a huge deal, as you should just use the reader method, but why create unnecessary inconsistency?
The first question is more interesting, how to metaprogramatically typecast. Your code is illustrating the common method of type coercion in ruby, namely the to_* methods that most objects provide. There's another way of doing this though:
String(:hello!) #=> "Hello"
Integer("123") #=> 123
Float("123") #=> 123.0
Array("1,2,3") #=> ["1,2,3"]
Now, these are interesting. It looks like what you're doing here is calling a nameless method on the classes, like String.(), which is how the [] syntax works on arguments. But that's not the case, you can't define a method named (). Instead these are actually methods defined on Kernel.
Therefore there are two ways of metaprogramatically calling them. The simplest is like so:
type = 'String'
Kernel.send(type,:hello) #=> "hello"
If no typecasting method exists you'll get a NoMethodError.
You could also get the method object and call it, like so:
type = 'String'
method(type).call(:hello) #=> "hello"
If the method doesn't exist in this case, you'll get a NameError.
The only real caveat for these is that, like all metaprogramming, you want to think through what you may be exposing. If there's an opportunity for user input to define the type attribute, then a malicious user could send you a payload like:
{type: 'sleep', value: 9999}
And now your code is going to call Kernel.send('sleep',9999), which would suck mightily for you. So you need to ensure that these type values are not something that can be set by any untrusted party, and/or whitelist the allowed types.
Keeping that caveat in mind, the following would be a fairly elegant way to solve your problem:
class BasicModel
def self.fields(hash={})
hash.each do |name,type|
define_method("#{name}"){ instance_variable_get "##{name"} }
define_method("#{name}=") {|val| instance_variable_set "##{name}", Kernel.send(type,val) }
end
end
fields name: String, qty: Integer, weight: Float
end
Note also, I'm defining instance variables (#name, #qty, #weight) rather than a fields hash, as I personally don't like when a metaprogramming macro like this depends on the initialize method to function correctly.
There's an added benefit if you don't need to override the initializer, you could actually extract this to a module and extend it in any class where you want to provide this behavior. Consider the following example, this time with whitelisting added to the allowed field types:
module Fieldset
TYPES = %w|String Integer Float|
def self.fields(hash={})
hash.each do |name,type|
raise ArgumentError, "Invalid Field Type: #{type}" unless TYPES.include?(type)
define_method("#{name}"){ instance_variable_get "##{name"} }
define_method("#{name}=") {|val| instance_variable_set "##{name}", Kernel.send(type,val) }
end
end
end
class AnyModel
extend Fieldset
fields name: String, qty: Integer, weight: Float
end
Great question. I hope this answer gives you some new ideas!
All you really need is a reference to the typecasting method used for each field. You can determine the typecasting method before you define the setter method and use send to do the typecasting when the setter is invoked.
Here is an example:
class BasicModel
def self.fields(params)
params.each do |name, type|
operator = case type.name
when 'Integer' then :to_i
when 'Float' then :to_f
when 'String' then :to_s
else raise 'invalid field type'
end
define_method(name) { #fields[name] }
define_method("#{name}=") do |val|
#fields[name] = val.send(operator)
end
end
end
def initialize
#fields = {}
end
end
I got an idea from #lastcanal and here is what I came up with:
class BasicModel
FieldTypes = Hash.new(StandardError.new('unsupported field type')).update(
String => :to_s,
Integer => :to_i,
Float => :to_f
)
def self.fields(params)
params.each do |name, type|
define_method("#{name}=") {|v| #fields[name] = v}
define_method(name) { #fields[name].send(FieldTypes[type]) }
end
end
def initialize
#fields = {}
end
end
BasicModel.fields(
name: String,
qty: Integer,
weight: Float
)
I would like to note that type casting in the attribute reader will not return
nil for unset attributes. Instead you will get nil converted to the
object type (i.e. nil.to_i => 0). To get nil for unset attributes
type cast in the attribute writer.

Ruby create methods from a hash

I have the following code I am using to turn a hash collection into methods on my classes (somewhat like active record). The problem I am having is that my setter is not working. I am still quite new to Ruby and believe I've gotten myself turned around a bit.
class TheClass
def initialize
#properties = {"my hash"}
self.extend #properties.to_methods
end
end
class Hash
def to_methods
hash = self
Module.new do
hash.each_pair do |key, value|
define_method key do
value
end
define_method("#{key}=") do |val|
instance_variable_set("##{key}", val)
end
end
end
end
end
The methods are created and I can read them on my class but setting them does not work.
myClass = TheClass.new
item = myClass.property # will work.
myClass.property = item # this is what is currently not working.
If your goal is to set dynamic properties then you could use OpenStruct.
require 'ostruct'
person = OpenStruct.new
person.name = "Jennifer Tilly"
person.age = 52
puts person.name
# => "Jennifer Tilly"
puts person.phone_number
# => nil
It even has built-in support to create them from a hash
hash = { :name => "Earth", :population => 6_902_312_042 }
planet = OpenStruct.new(hash)
Your getter method always returns the value in the original hash. Setting the instance variable won't change that; you need to make the getter refer to the instance variable. Something like:
hash.each_pair do |key, value|
define_method key do
instance_variable_get("##{key}")
end
# ... define the setter as before
end
And you also need to set the instance variables at the start, say by putting
#properties.each_pair do |key,val|
instance_variable_set("##{key}",val)
end
in the initialize method.
Note: I do not guarantee that this is the best way to do it; I am not a Ruby expert. But it does work.
It works just fine for me (after fixing the obvious syntax errors in your code, of course):
myClass.instance_variable_get(:#property) # => nil
myClass.property = 42
myClass.instance_variable_get(:#property) # => 42
Note that in Ruby instance variables are always private and you never define a getter for them, so you cannot actually look at them from the outside (other than via reflection), but that doesn't mean that your code doesn't work, it only means that you cannot see that it works.
This is essentially what I was suggesting with method_missing. I'm not familiar enough with either route to say why or why not to use it which is why I asked above. Essentially this will auto-generate properties for you:
def method_missing sym, *args
name = sym.to_s
aname = name.sub("=","")
self.class.module_eval do
attr_accessor aname
end
send name, args.first unless aname == name
end

Initialize a Ruby class from an arbitrary hash, but only keys with matching accessors

Is there a simple way to list the accessors/readers that have been set in a Ruby Class?
class Test
attr_reader :one, :two
def initialize
# Do something
end
def three
end
end
Test.new
=> [one,two]
What I'm really trying to do is to allow initialize to accept a Hash with any number of attributes in, but only commit the ones that have readers already defined. Something like:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
eval("##{opt} = \"#{val}\"")
end
end
Any other suggestions?
This is what I use (I call this idiom hash-init).
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| send("#{k}=", v) }
end
If you are on Ruby 1.9 you can do it even cleaner (send allows private methods):
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
end
This will raise a NoMethodError if you try to assign to foo and method "foo=" does not exist. If you want to do it clean (assign attrs for which writers exist) you should do a check
def initialize(object_attribute_hash = {})
object_attribute_hash.map do |(k, v)|
writer_m = "#{k}="
send(writer_m, v) if respond_to?(writer_m) }
end
end
however this might lead to situations where you feed your object wrong keys (say from a form) and instead of failing loudly it will just swallow them - painful debugging ahead. So in my book a NoMethodError is a better option (it signifies a contract violation).
If you just want a list of all writers (there is no way to do that for readers) you do
some_object.methods.grep(/\w=$/)
which is "get an array of method names and grep it for entries which end with a single equals sign after a word character".
If you do
eval("##{opt} = \"#{val}\"")
and val comes from a web form - congratulations, you just equipped your app with a wide-open exploit.
You could override attr_reader, attr_writer and attr_accessor to provide some kind of tracking mechanism for your class so you can have better reflection capability such as this.
For example:
class Class
alias_method :attr_reader_without_tracking, :attr_reader
def attr_reader(*names)
attr_readers.concat(names)
attr_reader_without_tracking(*names)
end
def attr_readers
#attr_readers ||= [ ]
end
alias_method :attr_writer_without_tracking, :attr_writer
def attr_writer(*names)
attr_writers.concat(names)
attr_writer_without_tracking(*names)
end
def attr_writers
#attr_writers ||= [ ]
end
alias_method :attr_accessor_without_tracking, :attr_accessor
def attr_accessor(*names)
attr_readers.concat(names)
attr_writers.concat(names)
attr_accessor_without_tracking(*names)
end
end
These can be demonstrated fairly simply:
class Foo
attr_reader :foo, :bar
attr_writer :baz
attr_accessor :foobar
end
puts "Readers: " + Foo.attr_readers.join(', ')
# => Readers: foo, bar, foobar
puts "Writers: " + Foo.attr_writers.join(', ')
# => Writers: baz, foobar
Try something like this:
class Test
attr_accessor :foo, :bar
def initialize(opts = {})
opts.each do |opt, val|
send("#{opt}=", val) if respond_to? "#{opt}="
end
end
end
test = Test.new(:foo => "a", :bar => "b", :baz => "c")
p test.foo # => nil
p test.bar # => nil
p test.baz # => undefined method `baz' for #<Test:0x1001729f0 #bar="b", #foo="a"> (NoMethodError)
This is basically what Rails does when you pass in a params hash to new. It will ignore all parameters it doesn't know about, and it will allow you to set things that aren't necessarily defined by attr_accessor, but still have an appropriate setter.
The only downside is that this really requires that you have a setter defined (versus just the accessor) which may not be what you're looking for.
Accessors are just ordinary methods that happen to access some piece of data. Here's code that will do roughly what you want. It checks if there's a method named for the hash key and sets an accompanying instance variable if so:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("##{opt}", val.to_s) if respond_to? opt
end
end
Note that this will get tripped up if a key has the same name as a method but that method isn't a simple instance variable access (e.g., {:object_id => 42}). But not all accessors will necessarily be defined by attr_accessor either, so there's not really a better way to tell. I also changed it to use instance_variable_set, which is so much more efficient and secure it's ridiculous.
There's no built-in way to get such a list. The attr_* functions essentially just add methods, create an instance variable, and nothing else. You could write wrappers for them to do what you want, but that might be overkill. Depending on your particular circumstances, you might be able to make use of Object#instance_variable_defined? and Module#public_method_defined?.
Also, avoid using eval when possible:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
instance_variable_set "##{opt}", val
end
end
You can look to see what methods are defined (with Object#methods), and from those identify the setters (the last character of those is =), but there's no 100% sure way to know that those methods weren't implemented in a non-obvious way that involves different instance variables.
Nevertheless Foo.new.methods.grep(/=$/) will give you a printable list of property setters. Or, since you have a hash already, you can try:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("##{opt}", val.to_s) if respond_to? "#{opt}="
end
end

Resources