How do I set a field dynamically for a Ohm object?
class OhmObj < Ohm::Model
attribute :foo
attribute :bar
attribute :baz
def add att, val
self[att] = val
end
end
class OtherObj
def initialize
#ohm_obj = OhmObj.create
end
def set att, val
#ohm_obj[att] = val #doesn't work
#ohm_obj.add(att, val) #doesn't work
end
end
The attribute class method from Ohm::Model defines accessor and mutator methods for the named attribute:
def self.attribute(name)
define_method(name) do
read_local(name)
end
define_method(:"#{name}=") do |value|
write_local(name, value)
end
attributes << name unless attributes.include?(name)
end
So when you say attribute :foo, you get these methods for free:
def foo # Returns the value of foo.
def foo=(value) # Assigns a value to foo.
You could use send to call the mutator method like this:
#ohm_obj.send((att + '=').to_sym, val)
If you really want to say #ohm_obj[att] = val then you could add something like the following to your OhmObj class:
def []=(att, value)
send((att + '=').to_sym, val)
end
And you'd probably want the accessor version as well to maintain symmetry:
def [](att)
send(att.to_sym)
end
[] and []= as a dynamic attribute accessor and mutator are defined by default in Ohm::Model in Ohm 0.2.
Related
I have a class with a private method:
class MyClass
attr_accessor :my_attr
def some_mth?(num)
# I want to use my_attr as a variale #myattr here
#and here i want to check if arr include num
#myattr.include?(num)
end
private
def some_pvt_mth
#myattr = [1,2,3,4]
for example generation array here
end
end
When I call #myattr inside some_mth, my variable #myattr is nil
How to use variable #myatt inside class, in every method is it possible?
How do I do it properly?
You do not need to define attr_accessor in order to use an instance variable within the defined class. It's purpose is to create a 'getter' and a 'setter' method, but those are only needed for other classes to access the data.
This is a class:
class Foo
def initialize
#my_attr = [1,2,3,4]
end
def attr_includes?(x)
#my_attr.include?(x)
end
end
There's no attr accessor, but this will work.
The attr accessor essentially includes this code in your class...
class Foo
def my_attr
#my_attr
end
def my_attr=(x)
#my_attr = x
end
end
But if you don't want that, you can just leave it out, and access the variable via other methods (such as your include example).
You have to define the instance variable value first:
class MyClass
attr_accessor :my_attr
def initialize
#myattr = [1, 2, 3, 4]
end
def some_mth?(num)
#myattr.include?(num)
end
end
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
I have a data object that contains dozens of attr_accessor fields for various inputs. Can I somehow define the class so that all setters for all fields will e.g. set the value as an empty string instead of the attempted nil?
Here's a little module to do it:
module NilToBlankAttrAccessor
def nil_to_blank_attr_accessor(attr)
attr_reader attr
define_method "#{attr}=" do |value|
value = '' if value.nil?
instance_variable_set "##{attr}", value
end
end
end
Just mix it in:
class Foo
extend NilToBlankAttrAccessor
nil_to_blank_attr_accessor :bar
end
And use it:
foo = Foo.new
foo.bar = nil
p foo.bar # => ""
foo.bar = 'abc'
p foo.bar # => "abc"
How it works
NilToBlankAttrAccessor#nil_to_blank_attr_accessor first defines the attr_reader normally:
attr_reader attr
Then it defines the writer by defining a method with the same name as the accessor, only with an "=" at the end. So, for attribute :bar, the method is named bar=
define_method "#{attr}=" do |value|
...
end
Now it needs to set the variable. First it turns nil into an empty string:
value = '' if value.nil?
Then use instance_variable_set, which does an instance variable assignment where the instance variable isn't known until runtime.
instance_variable_set "##{attr}", value
Class Foo needs nil_to_blank_attr_accessor to be a class method, not an instance method, so it uses extend instead of include:
class Foo
extend NilToBlankAttrAccessor
...
end
Instead of doing
object.foo = given_input
you should do
object.foo = given_input.nil? ? "" : given_input
or if you want to turn false into "" as well, then
object.foo = given_input || ""
Where is instance variable initialized as nil first time?
Can I redefine it as other value by default for all instances?
For example:
class Class
#some code here or maybe in an Object class
end
class Foo1
attr_accessor :bar
end
class Foo2
attr_accessor :bar
end
p Foo1.new.bar # result is not nil
p Foo2.new.bar # result is not nil
This can be done by modifying the reader:
class Class
def attr_accessor(attr_name)
...
define_method "#{attr_name}" do
if instance_variable_get "##{attr_name}_history"
instance_variable_get "##{attr_name}_history"
else
"Not nil"
end
end
...
end
end
But this doesn't help in understanding the core of Ruby.
Many thanks!
If you want to set default values, you can assign them in an initialize method of a class.
For example:
class Test
attr_accessor :bar
def initialize
#bar = 'bar'
end
end
Test.new.bar
# => "bar"
Remember that attr_accessor :bar gives you helper methods to set and get the underlying instance variable #bar.
If you want default values for lots of classes, you can have them inherit from a class that sets the instance variables as not nil:
class Foo < Test
end
Foo.new.bar
# => "bar"
Define a new method in class Class. Get instance variables through :instance_variables and set them to anything you like by using :instance_variable_set(:#var,default_value)
class Class
alias oldNew new
def new(*args)
result = oldNew(*args)
default = 2354 # set default here
a = result.instance_variables
a.each do
|d|
result.instance_variable_set(d,default)
end
return result
end
end
(Corrected following Jörg W Mittag's comment)
No you cannot. Instance variables are set evaluated to nil when you call them without assigning a value to them.
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.