In order to ask something like:
MyClass::create().empty?
How would I set up empty within MyClass?
Empty (true/false) depends on whether a class variable #arr is empty or not.
The question mark is actually part of the method name, so you would do this:
class MyClass
def empty?
#arr.empty? # Implicitly returned.
end
end
Exactly the same as I showed in the last post, but with a different method name.
First, create must return something with an empty? method. For example:
class MyClass
def self.create
[]
end
end
If you want to be operating on instances of MyClass as per your last question:
class MyClass
def self.create
MyClass.new
end
def initialize
#arr = []
end
def empty?
#arr.empty?
end
def add x
#arr << x
self
end
end
Here MyClass acts as a simple wrapper around an array, providing an add method.
pry(main)> MyClass.create.empty?
=> true
You might also need to check whether #arr is nil or not. This depends on your class definition of empty.
def empty?
!#arr || #arr.empty?
end
You could use Forwardable to delegate empty? from your class to the array:
require "forwardable"
class MyClass
extend Forwardable
def_delegators :#arr, :empty?
def initialize(arr)
#arr = arr
end
end
my_object = MyClass.new([])
my_object.empty? # => true
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'm trying to create an array of all the superclasses for the given class. I tried to solve this problem by using a loop. Why is this not working?
class Object
def superclasses
array = []
klass = self.superclass
unless klass == nil
array << klass
klass = klass.superclass
end
array
end
end
class Bar
end
class Foo < Bar
end
p Foo.superclasses # should be [Bar, Object, BasicObject]
unless isn't a loop. What you're looking for is until:
class Object
def superclasses
array = []
klass = self.superclass
until klass == nil
array << klass
klass = klass.superclass
end
array
end
end
class Bar
end
class Foo < Bar
end
p Foo.superclasses # Prints "[Bar, Object, BasicObject]"
Furthermore, you don't need a new method for this. There's already a method called Module#ancestors which does basically what you want:
class Bar
end
class Foo < Bar
end
p Foo.ancestors # Prints "[Foo, Bar, Object, Kernel, BasicObject]"
Note that the return value of ancestors includes Foo itself, and modules which have been included in the inheritance chain, like Kernel. If you don't want that, you can define superclasses like this:
class Module
def superclasses
ancestors[1..-1].select{|mod| mod.is_a? Class }
end
end
class Bar
end
class Foo < Bar
end
p Foo.superclasses # Prints "[Bar, Object, BasicObject]"
You need a loop to continue looking up superclasses:
def superclasses
array = []
klass = self.superclass
while klass
array << klass
klass = klass.superclass
end
array
end
I think it looks a bit nicer to use recursion
def superclasses
return [] if superclass.nil?
[superclass].concat superclass.superclasses
end
And really this method should be defined in Class because not every Object responds to superclass.
Ruby's built-in way of doing this is Module#ancestors. It's not exactly the same though, because that also takes into account included modules. The built-in way is the correct way to do this, however, since the strict chain of superclasses doesn't really give you the whole picture in Ruby.
Another way to get superclasses
class Object
def superclasses
ObjectSpace.each_object(Class).select { |klass| klass < self }
end
end
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?
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.