I want to be able to have a virtual attribute on a non-database model that is a hash. I just can't figure out what the syntax is for adding and removing items from this hash:
If I define:
attr_accessor :foo, :bar
then in a method in the model, I can use:
self.foo = "x"
But I can't say:
self.bar["item"] = "value"
Try
self.bar = Hash.new
self.bar["item"] = "value"
class YourModel
def bar
#bar ||= Hash.new
end
def foo
bar["item"] = "value"
end
end
but classic approach would be:
class YourModel
def initialize
#bar = Hash.new
end
def foo
#bar["item"] = "value"
end
end
Just use OpenStruct, Hash with Indifferent Access or Active Model.
When you are calling:
attr_accessor :foo, :bar
on your class, Ruby does something like the following behind the curtains:
def foo
return #foo
end
def foo=(val)
#foo = val
end
def bar
return #bar
end
def bar=(val)
#bar = val
end
The methods #foo and #bar are just returning the instance variables and #foo= and #bar= just set them. So if you want one of them to contain a Hash, you have to assign this Hash somewhere.
My favorite solution would be the following:
class YourModel
# generate the default accessor methods
attr_accessor :foo, :bar
# overwrite #bar so that it always returns a hash
def bar
#bar ||= {}
end
end
Related
Suppose I have a setup like this:
class Foo
attr_accessor :bar
def initialize
#bar = Bar.new
end
end
class Bar
def bar_method
self.class # => Bar
whatever???.class # => Foo
end
end
foo = Foo.new
foo.bar.bar_method
I know that I can set up the method like this:
def bar_method(selfs_self)
selfs_self.class # => Foo
end
And call the method like this: foo.bar.bar_method(foo) to get what I want. But that seems pretty redundant. Is there any way, inside of bar_method, that I can get a reference to foo, without specifically passing in a reference to it?
No.
Usually this is done by passing a reference to the parent object when initializing child objects, like:
class Foo
attr_accessor :bar
def initialize
#bar = Bar.new(self)
end
end
class Bar
attr_reader :foo
def initialize(foo)
#foo = foo
end
def bar_method
self.class # => Bar
foo.class # => Foo
end
end
I'm currently doing some metaprogramming with ruby, and I'm trying to isolate the methods of class (that class is in another file, that I get by a require). I can get all the methods, thanks to klass.public_instance_methods(false), but I in the sametime, the array given also have all the attributes of the class. How could I isolate them ? In others related questions on SO, they suggest to use klass.instance_variables but when I do that, it only returns an empty array.
I can't seem to wrap my head around that one. I don't understand why there isn't a method specifically for that already...
For example:
I have in a file this class :
class T
attr_reader:a
def initialize(a)
#a = a
end
def meth
#code here
end
end
And, in another file, i have
require_relative 'T.rb'
class meta
def initialize
methods = T.public_instance_methods(false) #=> here methods = [:a,:meth] but I would want only to have [:meth]
#rest of code
end
end
For class defined like this:
class Klass
attr_accessor :variable
def initialize(variable)
#variable = variable
end
def method
end
end
you can find public non-attr instance methods using public_instance_methods and instance_variables methods.
public_instance_methods = Klass.public_instance_methods(false)
# [:method, :variable, :variable=]
instance_variables = Klass.new(nil).instance_variables
# [:#variable]
getters_and_setters = instance_variables
.map(&:to_s)
.map{|v| v[1..-1] }
.flat_map {|v| [v, v + '=']}
.map(&:to_sym)
# [:variable, :variable=]
without_attr = public_instance_methods - getters_and_setters
# [:method]
This is impossible. Ruby's "attributes" are completely normal methods. There is no way to distinguish them from other methods. For example, these two classes are completely indistinguishable:
class Foo
attr_reader :bar
end
class Foo
def bar
#bar
end
end
You can try to be clever and filter them out based on instance variables, but that is dangerous:
class Foo
# can filter this out using #bar
attr_writer :bar
def initialize
#bar = []
end
end
class Foo
def initialize
#bar = []
end
# this looks the same as above, but isn't a normal attribute!
def bar= x
#bar = x.to_a
end
end
I have a ruby class, and in one of the methods, it calls an external function, and pass in all instance variables, and continue with the return value. Here is the code:
class MyClass
attr_accessor :name1
attr_accessor :name2
...
attr_accessor :namen
def inner_func():
all_vars = ???? # how to collect all my instance variables into a dict/Hash?
res = out_func(all_vars)
do_more_stuff(res)
end
end
The problem is the instance variables might vary in subclasses. I can't refer them as their names. So, is there a way to do this? Or Am I thinking in a wrong way?
You can use instance_variables to collect them in an Array. You will get all initialized instance variables.
class MyClass
attr_accessor :name1
attr_accessor :name2
...
attr_accessor :namen
def inner_func():
all_vars = instance_variables
res = out_func(all_vars)
do_more_stuff(res)
end
end
You could keep track of all accessors as you create them:
class Receiver
def work(arguments)
puts "Working with #{arguments.inspect}"
end
end
class MyClass
def self.attr_accessor(*arguments)
super
#__attribute_names__ ||= []
#__attribute_names__ += arguments
end
def self.attribute_names
#__attribute_names__
end
def self.inherited(base)
parent = self
base.class_eval do
#__attribute_names__ = parent.attribute_names
end
end
def attributes
self.class.attribute_names.each_with_object({}) do |attribute_name, result|
result[attribute_name] = public_send(attribute_name)
end
end
def work
Receiver.new.work(attributes)
end
attr_accessor :foo
attr_accessor :bar
end
class MySubclass < MyClass
attr_accessor :baz
end
Usage
my_class = MyClass.new
my_class.foo = 123
my_class.bar = 234
my_class.work
# Working with {:foo=>123, :bar=>234}
my_subclass = MySubclass.new
my_subclass.foo = 123
my_subclass.bar = 234
my_subclass.baz = 345
my_subclass.work
# Working with {:foo=>123, :bar=>234, :baz=>345}
I can't figure out the proper block initialize
class Foo
attr_accessor :bar
end
obj = Foo.new do |a|
a.bar = "baz"
end
puts obj.bar
Expect "baz"
instead get nil
What is the proper incantation for block class initializers in ruby?
Another way to make a block initializer would be writing it yourself one:
class Foo
attr_accessor :bar
def initialize
yield self if block_given?
end
end
And later use it:
foo = Foo.new do |f|
f.bar = true
end
My two cents.
Try again:
class Foo
attr_accessor :bar
end
obj = Foo.new.tap do |a|
a.bar = "baz"
end
puts obj.bar
I don't think new can take a block. Never saw it anywhere anyway. Why do you want to initialize in a block ? You can always do obj = foo.new.tap do |a| ... If you really want a block
actually you have a constructor for these purposes:
class Foo
attr_accessor :bar
def initialize(bar = "baz")
#bar = bar
end
end
I tried to to extend the code from this question for keeping records of an attribute value. However, my code fails in the case of more than one attributes. Here is the code:
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name
ah=attr_name+"_history"
attr_reader ah
class_eval %Q{
def #{attr_name}= (attr_name)
#attr_name=attr_name
if #ah == nil
#ah=[nil]
end
#ah.push(attr_name)
end
def #{ah}
#ah
end
def #{attr_name}
#attr_name
end
}
end
end
Here a dummy class for testing
class Foo
attr_accessor_with_history :bar
attr_accessor_with_history :bar1
end
f = Foo.new
f.bar = 1
f.bar = 2
f.bar1 = 5
p f.bar_history
p f.bar1_history
For some reason, f.bar and f.bar1 both return 5 and f.bar_history = f.bar1_history = [nil, 1, 2, 5]. Any idea why that is?
You were using #ah and #attr_name instead of ##{ah} and ##{attr_name} when getting/setting in the methods. This meant that they were always setting and returning the same instance variable, instead of different, dynamically named ones.
class Class
def attr_accessor_with_history(attr_name)
class_eval %{
attr_reader :#{attr_name}, :#{attr_name}_history
def #{attr_name}=(value)
##{attr_name} = value
##{attr_name}_history ||= [nil]
##{attr_name}_history << value
end
}
end
end
I've also generally cleaned up your code a little to make it (I think) clearer and more concise.