Ruby. Class vs instance variable methods - ruby

I have a class:
class MyClass
def self.create_array
variable = ['one', 'two', 'three']
# I had:
# variable.each {|v| v.upcase}
# but want to do:
second_method(variable)
# or like this:
variable.second_method
# of course without parameter 'var' in second option
end
def second_method (var)
var.map {|v| v.upcase}
end
end
puts MyClass.create_array
# of course:
=> undefined method `second_method' for MyClass:Class (NoMethodError)
So I am just wondering how to incorporate second_method into first one.
It only worked when I did:
class MyClass
def self.create_array
variable = ['one', 'two', 'three']
MyClass.second_method(variable)
end
def self.second_method (var)
var.map {|v| v.upcase}
end
end
puts MyClass.create_array
Why it works only when I call it on class? I would like to just call it on my variable. Please, enlighten me!
Edit
Would it make any sense to create method in:
class Array
def second_method
#content
end
end

You've defined second_method as an instance method on MyClass, which means it can only be called on an instance of MyClass - not on the class itself (that's why the second version works, where you define both methods as class methods using the self. operator.
To get your existing code to work, you could call MyClass.new.second_method(variable) inside self.create_array.

In Ruby only class methods can be called from an class method. when you are calling create_array in first snippet, you can not call a instance methds from inside it.

Like the others pointed out, class methods can only be called directly on the class itself.
Is it this you want to achieve ?
class MyClass
attr_accessor :variable
def initialize
#variable = ['one', 'two', 'three']
second_method #variable
end
def second_method var
#variable = var.map {|v| v.upcase}
end
end
MyClass.new
#<MyClass:0x292fda0 #variable=["ONE", "TWO", "THREE"]>
Or you can give the array as a parameters when instantiating the class like this
class MyClass
attr_accessor :variable
def initialize variable
#variable = variable
second_method #variable
end
def second_method var
#variable = var.map {|v| v.upcase}
end
end
MyClass.new ['one', 'two', 'three']
#<MyClass:0x292fda0 #variable=["ONE", "TWO", "THREE"]>

Related

Get attr_reader, writer, or accessor oustide of the class

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

ruby: how to initialize class method with parameters?

I'm struggling with a simple question how to instantiate a class with arguments.
For example i have a class with an initializer that takes two arguments and a class method:
class MyClass
attr_accessor :string_1, :string_2
def initialize(string_1, string_2)
#string_1 = string_1
#string_2 = string_2
end
def self.some_method
# do something
end
end
If some_method were an instance method i could instantiate a new object of MyClass and call the instance method like:
MyClass.new("foo", "bar").some_method
But how can i achieve that for the MyClass itself and the class method instead of an instance?
Something like MyClass.self("foo", "bar").some_method does not work.
You could do this.
class MyClass
singleton_class.send(:attr_accessor, :string_3)
end
MyClass.string_3 = "It's a fine day."
MyClass.string_3 #=> "It's a fine day."
#string_3 is a class instance variable.
I believe the conventional way to initiate a class and then run a method on those values would be like:
class MyClass
def initialize(string_1, string_2)
#string_1 = string_1
#string_2 = string_2
end
def some_method
"#{#string_1} #{#string_2}"
end
end
a = MyClass.new("foo", "bar")
puts a.some_method #=> "foo bar"
If you want to use attr_accessor then you can bypass the some_method to return those values:
class MyClass
attr_accessor :string_1, :string_2
def initialize(string_1, string_2)
#string_1 = string_1
#string_2 = string_2
end
end
a = MyClass.new("foo", "bar")
puts a.string_1 + a.string_2 #=> "foobar"

Setting not initialized instance variable

Is it elegant to use instance variables in a class which are not initialized and setting them using other methods? Or maybe there is a better way to do that?
class Klass
def initialize(a)
#a = a
end
def set_b(b)
#b = b
end
end
In contrast to other languages, If you do not initialize an instance variable it will always be nil (whereas in certain other languages you could get something undefined).
As long as other methods of Klass do not depend on the instance variable actually having a value, this should be ok.
As for getters and setters, there are attr_accessor, attr_reader and attr_writer (see the docs).
class Klass
attr_accessor :b
# there's also attr_reader and attr_writer
def initialize(a)
#a = a
end
end
k = Klass.new :foo
k.b = :bar
k.b
#=> :bar
k.a
#=> undefined method `a' for #<Klass:0x007f842a17c0e0 #a=:foo, #b=:bar> (NoMethodError)
The way you are doing it works but Ruby defined attr_accessor, attr_reader and attr_writer for that purpose.
attr_reader: create method to read 'a'
attr_writer: create method to write 'a'
attr_accessor: create methods to read and write 'a'
I think the best way to do that is to use attr_accessor:a
class Klass
attr_accessor:a
def initialize(a)
#a = a
end
end
Then you can do:
k = Klass.new "foo" #example
k.a = "bar"

understand self for attr_accessor class method

class Test
class << self
attr_accessor :some
def set_some
puts self.inspect
some = 'some_data'
end
def get_some
puts self.inspect
some
end
end
end
Test.set_some => Test
puts Test.get_some.inspect => Test nil
Here above I could find self as Test itself but not returning the some_data as output.
But while I modified in following way it returns expected output
class Test
class << self
attr_accessor :some
def set_some
puts self.inspect
self.some = 'some_data'
end
def get_some
puts self.inspect
self.some
end
end
end
Test.set_some => Test
puts Test.get_some.inspect => Test some_data
What is the differences?
EDIT
Now in the first example if I set as get some method as
Test.some = 'new_data'
puts Test.some.inspect #=> new_data
Test.set_some
puts Test.get_some.inspect => new_data
Now it made me much more confused.
some = :foo makes ruby think it should create a new local variable with name some. If you want to call some=(), you have to use an explicit reciever - as in self.some = :foo. I once lost a bet on that... :-/
It's (local) variable in the first example
In the first example some is a local variable.
In the second one, some is a method of self. Why? Because attr_accessor :some is the same as:
def some= (val)
#some = val
end
def some
return #some
end
So, you have created the getter and setter methods for the instance variable #some (it's an instance variable of the object Test, as every class is also an object of class Class).
in the first method
def set_some
puts self.inspect
some = 'some_data'
end
some is a local variable.. its not the same as #some that is a instance variable (in this case a class instance variable) so the value disappears when the method ends.
if you want to call the setter method some or set #some to something then do this
#some = 'some_data'
or
self.some = 'some_data'
in the second method
def get_some
puts self.inspect
self.some
end
your calling the method some. which returns the instace variable #some.. and since at this point #some has no value.. returns nil..
Example 1 with no method override and no local variable
class Foo
def initialize
#foo = 'foo'
end
def print_foo
print #foo
print self.foo
print foo
end
end
#foo, self.foo, and foo will access instance variable #foo within the instance method:
Foo.new.print_foo #=> foofoofoo
Example 2 with method override
class Foo
def initialize
#foo = 'foo'
end
def foo
return 'bar'
end
def print_foo
print #foo
print self.foo
print foo
end
end
#foo will access the instance variable, but self.foo and foo will call the foo override method:
Foo.new.print_foo #=> foobarbar
Example 3 with method override and local variable
class Foo
def initialize
#foo = 'foo'
end
def foo
return 'bar'
end
def print_foo
foo = 'baz'
print #foo
print self.foo
print foo
end
end
#foo accesses instance variable, self.foo accesses override method, and foo accesses local variable:
Foo.new.print_foo #=> foobarbaz

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.

Resources