I think I'm going a bit crazy when trying to understand instance variables in Ruby. My only aim here is to make sure that every object created for a given class has a variable with a predetermined value without writing an initialize method for that class. Something like:
class Test
#my = []
attr_accessor :my
end
t = Test.new
t.my # I want [] but this shows nil
Is it possible to achieve this without touching initialize ? Thanks.
EDIT: To clarify, I'm writing some piece of code which will be executed similar to attr_accessor in the sense that it'll add an instance variable to the class in which it is executed. If I write my own initialize, I will end up clobbering the one written by the user.
What you are doing is defining an instance variable on the class level (Since classes are instances of the Class class, this works just fine).
And no, there is no way around initialize.
Edit: You have a little misconception in your edit. attr_accessor doesn't add an instance variable to the class. What it does, literally, is this (using your example of my):
def my; #my; end
def my=(value); #my = value; end
It doesn't actively create/initialize any instance variable, it just defines two methods. And you could very well write your own class method that does similar things, by using define_method.
Edit 2:
To further illustrate how one would write such a method:
class Module
def array_attr_accessor(name)
define_method(name) do
if instance_variable_defined?("##{name}")
instance_variable_get("##{name}")
else
instance_variable_set("##{name}", [])
end
end
define_method("#{name}=") do |val|
instance_variable_set("##{name}", val)
end
end
end
class Test
array_attr_accessor :my
end
t = Test.new
t.my # => []
t.my = [1,2,3]
t.my # => [1, 2, 3]
# as instance variable without initialize
class Test1
def my; #my ||= [] end
attr_writer :my
end
t = Test1.new
t.my
# as class instance variable
class Test2
#my = []
class << self; attr_accessor :my end
end
Test2.my
I don't think it is, why are you so hesitant to just write a quick initialize method?
Related
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 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
I've got some troubles with Ruby about callbacks (and inheritance). Here is my code:
class Lmao
def initialize
#str = "HAHAHAHAHAHHAHAHAH"
#before_laughing = []
end
def self.inherited(base)
base.extend(Callbacks)
end
def laughing
#before_laughing.each {|method| send(method) }
#str
end
end
module Callbacks
def before_laughing(*methods)
#before_laughing = methods
end
end
class Lol < Lmao
before_laughing :downcase_please
def downcase_please
#str.downcase!
end
end
a = Lol.new
a.laughing # => "HAHAHAHAHAHHAHAHAH"
And as you can see, my before laughing callback don't work... because the array #before_laughing is empty. I believe this can be fixed by editing the way I save *methods into an Lol's instance method (from inside Callbacks). But I don't really see how...
If you know the solution, thanks for your light!
Thanks to Mon_Ouie, the solution is:
class Lmao
def initialize
#str = "HAHAHAHAHAHHAHAHAH"
end
def self.inherited(base)
base.extend(Callbacks)
end
def laughing
self.class.callbacks_before_laughing.each {|method| send(method) }
#str
end
end
module Callbacks
def before_laughing(*methods)
#before_laughing = methods
end
def callbacks_before_laughing
#before_laughing
end
end
class Lol < Lmao
before_laughing :downcase_please
def downcase_please
#str.downcase!
end
end
Pretty awesome.
There are two different instance variables called #before_laughing in your code: one is an instance variable of instances of the Lmao class, which gets initialized to [] (i.e. an empty Array) in Lmao's initialize instance methods and gets read in Lmao's laughing instance method. However, since the only place this instance variable gets written to is the initializer, it will always be an empty Array.
The other instance variable is an instance variable of the Lol class object itself, which gets set to the Array [:downcase_please] inside of the before_laughing method. This one, however, never gets read.
How can a class method (inside a module) update an instance variable? Consider the code bellow:
module Test
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def update_instance_variable
#temp = "It won't work, bc we are calling this on the class, not on the instance."
puts "How can I update the instance variable from here??"
end
end
end
class MyClass
include Test
attr_accessor :temp
update_instance_variable
end
m = MyClass.new # => How can I update the instance variable from here??
puts m.temp # => nil
You'd have to pass your object instance to the class method as a parameter, and then return the updated object from the method.
That does nto quite make sense.
You use the initialize method to set default values.
class MyClass
attr_accessor :temp
def initialize
#temp = "initial value"
end
end
The initialize method is automatically run for you when you create a new object.
When your class declaration is run, there are no, and cannot be any, instances of the class yet.
If you want to be able to change the default values later you can do something like this:
class MyClass
attr_accessor :temp
##default_temp = "initial value"
def initialize
#temp = ##default_temp
end
def self.update_temp_default value
##default_temp = value
end
end
a = MyClass.new
puts a.temp
MyClass.update_temp_default "hej"
b = MyClass.new
puts b.temp
prints
initial value
hej
If you also want that to change already created instances' variables you need additional magic. Please explain exactly what you wish to accomplish. You are probably doing it wrong :)
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.