I'm a little confused about scope of variables, in ruby I wrote a test program:
class Test
attr_reader :tester
def initialize(data)
#tester = data
end
def getData
tester
end
end
puts Test.new(11).getData
now this works fine, the attr_reader, but my confusion is that since I've define attr_reader :tester then why can't I go tester = data rather then #tester = data, because when retrieving the data in getData I only have to write tester and not #tester
Using attr_reader is equivalent to
class Test
def initialize(data)
#tester = data
end
# attr_reader defines this method for you
def tester
#tester
end
def getData
tester
end
end
In your getData method using tester is equivalent to self.tester. If you use #tester you access the variable directly. When you use tester you access the variable via the getter method.
attr_reader means that should read: "The corresponding instance variable getter and setter
methods will be created for you." so that first we get data and then we set that data.
some_name = syntax without the explicit receiver is interpreted as a local variable assignment. In order to do an assignment to an instance variable, you have to explicitly set the receiver even if it is self. In this case, self.tester =.
Related
I was wondering if there is a simplest way to get rid of the "self" when calling a function from another class.
Example, I have here a class that has a function.
module Portfolio
class Main < Sinatra::Base
def self.create_user(username,password,confirm_pass,fullname)
#creation_flag = false
begin
if password == confirm_pass
#creation_flag = User.create(username: username,password: password,full_name: fullname).valid?
end
rescue Exception => e
puts 'Error Occured: '+e.message,""
end
return #creation_flag
end
def self.
end
end
to use this i need to declare self.create_user(params goes here)
is there a way to get rid of the self?.
Thanks in advance.
There's nothing wrong with using self, but it bypasses the requirement to create a variable instance of your object, so some die-hard OO programmers would suggest avoiding self for that reason. If you avoid "self" then you are forced to initialize your class and assign it to a variable name which forces you think of it as a true object, and not just a collection of functions.
Here's an example class to demonstrate how you would call methods with and without "self"
class StaticVersusObjectMethod
def self.class_method
puts 'Hello, static class method world!'
end
def object_method
puts 'Hello, object-oriented world!'
end
end
# No need to create an object instance variable if the method was defined with 'self'
StaticVersusObjectMethod.class_method
# You must create an object instance variable to call methods without 'self'
object = StaticVersusObjectMethod.new
object.object_method
output:
Hello, static class method world!
Hello, object-oriented world!
Whether you use self in the declaration should depend on the data you want your method to use. If the methods will only operate on the variables you pass in as parameters, then use 'self'. On the other hand, don't use 'self' if you want them to act as true object methods. "True" object methods can operate on the state of the class variables (fields) in the objects which you create and assign to a one or more variable names.
class Artist
##song_count = []
attr_accessor :name, :songs
def initialize(name)
#name = name
#songs = []
end
def add_song(song)
#songs << song
end
def print_songs
songs.each {|song| puts song.name}
end
end
So in this example, it uses all two types, #songs and songs.
I'm having a hard time understanding why these are used, instead of using #songs for everything.
And then in this example,
def add_song(song)
self.songs << song
song.artist = self
##song_count +=1
end
Why is self.songs used instead of #songs?
Ok, so I forgot to say one more thing. In the first code snippet above,for method print_songs, why am I able to use songs.each instead of #songs.each? I was expected it to generate an error undefined songs.
Why is self.songs used instead of #songs
Using the method is more flexible. You're abstracting yourself from knowing how exactly it gets/stores data. The less you rely on implementation details, the easier it will be for you to change code later.
One small example, consider this implementation of songs
def songs
#songs ||= []
#songs
end
#songs may or may not have been assigned value prior to invocation of this method. But it doesn't care. It makes sure that #songs does have a sane default value. The concept is called "lazy initialization" and it's very tedious and error-prone to do if you use instance variables directly.
So, when in doubt, always use methods.
Difference between foo and #foo
Instance variables
Instance variables are defined within instance methods, and their names begin with #. Their value is only accessible within the specific object on which it was set. In other words, when we modify the value of an instance variable, the change only applies to that particular instance. Unlike local variables which are only available within the method where they were defined, instance variables are accessible by all methods within the object (instance methods of the class). Instance variables are the most commonly used type of variable in Ruby classes.
class Car
attr_reader :color
def set_color(color_receiverd_as_argument)
#color = color_receiverd_as_argument
end
end
car1 = Car.new
car1.color # Output: => nil
car1.set_color "black"
car1.color # Output: => "black"
car2 = Car.new
car2.set_color "silver"
car2.color # Output: => "silver"
In the example above, notice that:
Trying to access an instance variable before it's initialized will not raise an exception. Its default value is nil.
Changing the value of the color variable in one instance of the Car class does not affect the value of the same variable in the other instances.
Local variables
A local variable within a class is like any other local variable in Ruby. It is only accessible within the exact scope on which it's created. If defined within a method, it is only available inside that method.
class Car
def initialize
wheels = 4
end
def print_wheels
print wheels
end
end
c = Car.new
c.print_wheels # Output: NameError: undefined local variable or method `wheels'…
The self keyword
The self keyword is always available, and it points to the current object. In Ruby, all method calls consist of a message sent to a receiver. In other words, all methods are invoked on an object. The object on which the method is called is the receiver, and the method is the message. If we call "foo".upcase, the "foo" object is the receiver and upcase is the message. If we don't specify an object (a receiver) when calling a method, it is implicitly called on the self object.
Self keyword at class level
When used within a class but outside any instance methods, self refers to the class itself.
class Foo
##self_at_class_level = self
def initialize
puts "self at class level is #{##self_at_class_level}"
end
end
f = Foo.new # Output: self at class level is Foo
Self keyword at instance methods
When inside an instance method, the self keyword refers to that specific instance. In other words, it refers to the object where it was called.
class Meditation
def initialize
puts "self within an instance method is #{self}"
end
end
zazen = Meditation.new # Output: self within an instance method is #<Meditation:0x00000000ab2b38>
Notice that #<Meditation:0x00000000ab2b38> is a string representation of the zazen object, which is an instance of the Meditation class.
Is there a way to bind an existing method to an existing instance of an object if both the method and the instance are passed as symbols into a method that does that if the instance is not a symbol?
For example:
def some_method
#do something
end
some_instance = Klass.new(something)
def method_that_binds(:some_method, to: :some_instance)
#how do I do that?
end
Your requirements are a little unusual, but it is possible to do this mostly as you say:
class Person; end
harry = Person.new
barry = Person.new
def test
puts 'It works!'
end
define_method :method_that_binds do |a_method, to|
eval(to[:to].to_s).singleton_class.send(:define_method, a_method, &Object.new.method(a_method))
end
method_that_binds :test, to: :harry
harry.test
# It works! will be sent to STDOUT
barry.test
# undefined method 'test'
This doesn't actually use a named parameter, but accepts a hash with a to key, but you can see you can call it in the way you want. It also assumes that the methods you are defining are defined globally on Object.
The API you want doesn't easily work, because you have to know from which scope you want to access the local variable. It's not quite clear to me why you want to pass the name of the local variable instead of passing the content of the local variable … after all, the local variable is present at the call site.
Anyway, if you pass in the scope in addition to the name, this can be accomplished rather easily:
def some_method(*args)
puts args
puts "I can access some_instance's ivar: ##private_instance_var"
end
class Foo; def initialize; #private_instance_var = :foo end end
some_instance = Foo.new
def method_that_binds(meth, to:, within:, with: [])
self.class.instance_method(meth).bind(within.local_variable_get(to)).(*with)
end
method_that_binds(:some_method, to: :some_instance, within: binding, with: ['arg1', 'arg2'])
# arg1
# arg2
# I can access some_instance's ivar: foo
As you can see, I also added a way to pass arguments to the method. Without that extension, it becomes even simpler:
def method_that_binds(meth, to:, within:)
self.class.instance_method(meth).bind(within.local_variable_get(to)).()
end
But you have to pass the scope (Binding) into the method.
If you'd like to add a method just to some_instance i.e. it's not available on other instances of Klass then this can be done using define_singleton_method (documentation here.)
some_instance.define_singleton_method(:some_method, method(:some_method))
Here the first use of the symbol :some_method is the name you'd like the method to have on some_instance and the second use as a parameter to method is creating a Method object from your existing method.
If you'd like to use the same name as the existing method you could wrap this in your own method like:
def add_method(obj, name)
obj.define_singleton_method(name, method(name))
end
Let's say we have a class A with a method a and a local variable c.
class A
def a; 10 end
end
c = '5'
And we want to add the method A#a to c.
This is how it can be done
c.singleton_class.send :define_method, :b, &A.new.method(:a)
p c.b # => 10
Explanations.
One way to add a method to an object instance and not to its class is to define it in its singleton class (which every ruby object has).
We can get the c's singleton class by calling the corresponding method c.signleton_class.
Next we need to dynamically define a method in its class and this can usually be accomplished by using the define_method which takes a method name as its first argument (in our case :b) and a block. Now, converting the method into a block might look a bit tricky but the idea is relatively simple: we first transform the method into a Method instance by calling the Object#method and then by putting the & before A.new.method(:a) we tell the interpreter to call the to_proc method on our object (as our returned object is an instance of the Method, the Method#to_proc will be called) and after that the returned proc will be translated into a block that the define_method expects as its second argument.
class MyClass
def instance_variable=(var)
puts "inside getter"
instance_variable = var
end
def function_1
self.instance_variable = "whatever"
end
def function_2
#instance_variable = "whatever"
end
end
myclass = MyClass.new
myclass.function1
results wiht "inside getter" on the console
myclass.function2
does not.
Im new to Ruby, do not know the difference, couldnt find it on the web...
Thanks in advance!
EDIT:
I assumed that by appending the "=", I overwrite a getter method for an implicitly defined instance variable "instance_variable."
That's also the reason why I called it that way.
Im not used to be allowed to use "=" in function names.
Thats why I assumed it would had some special meaning.
Thanks for your help.
EDIT2:
I just thought I really overwrite the assignment and not only the getter. I got it all mixed up.
Sorry and Thanks.
You have (misleading) named your setter instance_variable. It is not an instance variable, it is a method that sets an instance variable.
When you call self.instance_variable= you are calling that method. When you set #instance_variable directly you are setting the variable itself, and that is why the setter method is not called.
A more idiomatic naming convention would be something like:
def name=(value)
#name = value
end
Of course, for simply, pass-through type getters and setters you can use
attr_reader :name #generates getter only
attr_writer :name #generates setter only, not very common
attr_accessor :name #generates getter and setter
The above methods are syntactic sugar which generate the get and/or set methods for you. They can be overriden later to provide additional functionality if needed.
EDIT: I see that you have made an update and just wanted to point out that this method doesn't set an instance variable at all:
def instance_variable=(var)
puts "inside getter"
instance_variable = var
end
In this case instance_variable is simply a local variable and will be discarded as soon as the method exits. Local variables take precedence over instance methods, and instance variables always begin with a # symbol.
This method:
def format_stations_and_date
from_station.titelize! if from_station.respond_to?(:titleize!)
to_station.titleize! if to_station.respond_to?(:titleize!)
if date.respond_to?(:to_date)
date = date.to_date
end
end
Fails with this error when date is nil:
NoMethodError (You have a nil object when you didn't expect it!
The error occurred while evaluating nil.to_date):
app/models/schedule.rb:87:in `format_stations_and_date'
app/controllers/schedules_controller.rb:15:in `show'
However, if I change date = date.to_date to self.date = self.date.to_date, the method works correctly.
What's going on? In general, when do I have to write self?
Edit: It's not related to the question, but please note that there is no "titleize!" method.
Whenever you want to invoke a setter method on self, you have to write self.foo = bar. If you just write foo = bar, the ruby parser recognizes that as a variable assignment and thinks of foo as a local variable from now on. For the parser to realize, that you want to invoke a setter method, and not assign a local variable, you have to write obj.foo = bar, so if the object is self, self.foo = bar
You disambiguiate between the instance method name and a local variable using self (it is allowed to have both with the same name in the same scope). In other words, there will be a method name resolution only if there is no local or block variable of the same name in scope. Behold:
class Foo
attr_accessor :boo
def do_boo
boo = 123
puts "Locvar: #{boo} Method: #{self.boo}"
end
end
Foo.new.do_boo
Here's why: imagine you have a module which implements a method. This method assigns something to it's internal local variable
"foo" which is used for some computation. If you skip the "self" part, the method will make a "foo=" method call on the object
whose class includes the module, which was not the intention of the author and can be downright disastrous.
class Foo
def bar=(new_value_of_bar)
set_off_nukes(new_value_of_bar / 3)
end
end
module InnocentModule # written by a different author elsewhere
def do_useful_stuff
...
bar = Math.sin(something) # we're dead
end
end
Foo.send(:include, InnocentModule)
Another crucial part where you have to use self is when invoking the Object#class method, because simply saying "class" means a class keyword for Ruby.