Difference between self.element = 'this' and self.send("element=", 'this') - ruby

I am trying to understand why these two things return different values.
Value is a string, and field is a text_field.
def populate_text(field, value)
self.send "user_name=", value
end
# => nil
def populate_text(value)
self.user_name = value
end
# => "value"
Why do self and send have different return values?
This class includes PageObject if that helps.

Ruby's syntax sugar for calling methods whose name ends with = always returns the righthand value, regardless of the return value of the method.
This is not the case when you use send to invoke the method. For example:
class Foo
def bar=(n)
:ohno
end
end
f = Foo.new
x = (f.bar = 42)
y = f.send("bar=", 42)
p [x,y]
#=> [42, :ohno]
So, you would get two different values if your user_name= method has a return value that is not the argument to the method.

Self.Send allows you to dynamically choose your objects, regardless of type.
This lets you data drive your test with very simple code.

Related

Struct with and without member

I'm a new Rubyist, currently I'm going to use the Struct in class in order to create a temporary object. However, I encounter a question when I use a member in Struct like this:
class A
attr_accessor :b
B = Struct.new(:name, :print_name) do
def print_name
p "Hello #{name}"
end
end
def initialize(input_name)
#b = B.new(input_name)
end
end
a = A.new('Leo')
a.b.print_name # Hello Leo
But I also receive the same result when my Struct B params don't include :print_name.
B = Struct.new(:name) do
...
end
So what is the different ? And when should I use the member param and when I'm not ?
Thanks
In first case you define a class which's initializer takes two arguments - name and print_name.
In second case you define a class which's initializer takes single argument - name.
These has nothing to do with the fact, that you are defining an instance method called print_name.
So instances of both classes (with and without print_name argument) have print_name method defined, thus both examples work identically.
The difference will be visible when you inspect created objects:
# first case with two arguments
foo = B.new(:a, :b)
foo.inspect
=> "#<struct B name=:a, print_name=:b>"
# second case with single argument
foo = B.new(:a)
foo.inspect
=> "#<struct B name=:a>"
Also, when you'll check instance methods of B class for both cases, you can see the difference:
# first case with two arguments
B.instance_methods false
#=> [:name, :name=, :print_name, :print_name=]
# second case with single argument
B.instance_methods false
#=> [:name, :name=, :print_name]
But I also receive the same result when my Struct B params don't
include :print_name
The difference, is that in first case you are able to do the following:
a.b.print_name = 'new print name'
a.b.inspect
#=> "#<struct B name='Leo', print_name='new print name'>"
Whereas in second case it will fail:
a.b.print_name = 'new print name'
#=> NoMethodError: undefined method 'print_name=' for #<struct B name='Leo'>

What is the "other" object and how does it work?

I have seen other used often in class comparisons, such as
def ==(other)
...
end
or
def eql?(other)
self == other
end
but I still have found no explanation of what it actually is. What's going on here?
And perhaps this is for another question, but what does starting a method with == imply?
In ruby, operators are in fact method calls. If you have two variables a and b and want to check their equality, you generally write a == b, but you could write a.==(b). The last syntax shows what happens during an equality check : ruby calls a's method == and passes it b as an argument.
You can implement custom equality check in your classes by defining the == and/or the eql? methods. In your example, other is simply the name of the argument it receives.
class Person
attr_accessor :name
def initialize name
#name = name
end
end
a = Person.new("John")
b = Person.new("John")
a == b # --> false
class Person
def == other
name == other.name
end
end
a == b # --> true
For your second question, the only methods starting with == you're allowed to implement are == and ===. Check here for the full list of restrictions on method names in ruby: What are the restrictions for method names in Ruby?
other is a parameter to this method, the object, that is being passed.
For example:
class A
def ==(other)
:lala == other
end
end
obj = A.new
obj.==(:foo) # full syntax, just like any other method
# but there's also a shorthand for operators:
obj == :foo # => false
obj == :lala # => true
other is the parameter for == and it represents the object you are comparing with.
Example
x == y
The == method (yes, its just a method!), on your x object, gets called with y as a parameter.
Welcome to Ruby, you'll love it after a while :)
other, in this case, is the object you're comparing to, so this:
class SomeClass
def ==(other)
self == other
end
end
means:
SomeClass.new == nil # here "other" is nil, this returns false
Basically def ==(other) is the implementation of the == operator, for cases where you do something specific in your class regarding comparison using ==.
For numbers you can get the sum of 2 numbers using:
a= x + y
in Ruby everything is object, Right! so x and y are objects and for Number(Object) x it has a defined method + which accept 1 paramter which is y
same for what you are trying to understand, what if you have 2 classes and you want to check if they are equal or not and its your defined class and you want to specify how the are equal for example if their name attribute is equal then you can say:
class Student
def ==(other)
self.name == other.name
end
end
fi = Student.new(name: 'Mark')
sec = Student.new(name: 'Hany')
if (fi == sec)
# do something here

Why does my method predefine the argument `(num=nil)`?

Please explain what the purpose of (num=nil) on line 1 is. I am trying to replicate the Enumerable module and was taken aback by the #count method.
def my_count(num = nil)
c = 0
if block_given?
my_each { |i| c += 1 if yield(i) }
elsif num.nil?
c = length
else
my_each { |i| c += 1 if i == num }
end
c
end
If you check the Enumerable#count documentation you will notice that you can call count with no arguments, with an argument or with a block.
The 3 if-conditions in your code reflects these 3 different ways to call the method.
The reason you have (num = nil) is to provide the ability to omit the num parameter when calling count. In no explict value for num is given, then it defaults to nil (and it will fall into the second if-condition num.nil?).
Whenever you define a method in Ruby, you can make it have arguments. When the method is called, the arguments are replaced by objects of your choosing. When you define an argument in the method definition, it becomes the default value.
For example, if you simply call my_count in a program, it will automatically choose num to be nil, since no argument was explicitly provided. Otherwise, if you called my_count(5), then num in the method will be 5.
Here is another example, with the default value as an empty hash. I will create two Server objects:
class Server
attr_accessor :name
attr_accessor :files
def initialize(name, files={})
#name = name
#files = files
end
end
# Supply hash of files
Server.new('hostname', {'filename' => '2015-08-21 13:49 -4000'})
# New server will have no files -- empty hash
Server.new('hostname')
Let's change the initialize method and create one last server:
class Server
attr_accessor :name
attr_accessor :files
def initialize(name, files) # No default value for files
#name = name
#files = files
end
end
Server.new('hostname', 'path/to/files/')
# => ArgumentError: wrong number of arguments (1 for 2)
In the modified class, I only supplied 1 argument, the name. Because files no longer has a default value, then not including the argument raises an error. To clarify, omitting an argument is only allowed when the method definition has a default value for the argument. Without a default value, nothing will be used (not nil, 0, or ''), and the program will stop/crash -- an error has been raised/encountered.
It is possible to create a method without arguments. Here is an example using String#length. You will see that .length does not take an argument, and instead works on the string.
string = 'Hello, world!'
string.length
# => 13

Local variables and methods with the same name in Ruby?

def gen_times(factor) do
return Proc.new {|n| n*factor}
end
gen_times.class # ArgumentError 0 for 1
gen_times(3).class # Proc
gen_times = 2
gen_times.class # Fixnum
times3 = gen_times(3) # A normal, working Proc
The first gen_times.class gives an ArgumentError, so I assume it returns the class name of gen_times's return value, which is confirmed in the next line.
But then, I assign gen_times, and it becomes a Fixnum. However, I can still use gen_times to return Procs.
I recall that Fixnum objects have immediate values, and that the object itself is used in assignment, rather than a reference to it.
So, is it right to say that gen_times is a Fixnum object that refers to a method?
In ruby you can have local variables and methods with the same name. This has some complications for example with setter methods in classes:
class Test
def active
#active
end
def active=(value)
#active = value
end
def make_active
active = true
end
end
t1 = Test.new
t1.active = true
t1.active #=> true
t2 = Test.new
t2.make_active
t2.active #=> nil
Code for t1 object will return expected result, but code for t2 returns nil, because make_active method is actually creating local variable and not calling active= method. You need to write self.active = true to make this work.
When you write gen_class, ruby tries to access local variable, if it is not defined ruby tries to call method. You can call your method explicit by writing gen_class().

assigning a value to many members of a class

class A
attr_accessor :dab
....
end
Now I have an array of instances of A, say
arr = [A.new, A.new, A.new]
And now I want to set a value to all instances of class A present in the array arr. Is there a shortcut in ruby/rails to do this?
In addition, I do have A inherited from ActiveRecord::Base
And my actual need is:
A.find_all_by_some_condition.all.dabs = 2
So, all found objects will have dab set to 2.
Is there shortcut for this?
To get the items of class A from an array you can use select/find_all
arr.select { |el| el.class == A } or arr.select { |el| A === el }
To achieve your actual result though you are looking to assign a value to several objects, not their corresponding class. class A does not define the actual objects it just defines the blueprint that the objects use when getting created. So finding a way to assign a value of all instances of A is not what you are after (although I might have missed the point of what you were asking for)
To assign a value to an array of object this works:
A.find_all_by_some_condition.each { |a| a.dab = 2 }
Perhaps you want to save them after that, now arr.each(&:save) might come in handy. Go look up the ampersand if you don't know it already. Very useful.
You can't do that directly by default, however you could build something like that using Ruby's method_missing.
Two solutions:
Solution 1 - Use a wrapper class
We'll call this class MArray for multi-assign-array.
class MArray
def initialize(inner_array)
#inner = inner_array
end
def method_missing(meth, value)
# Check if assignement, and if it is then run mass-assign
if meth.to_s =~ /^\w+=$/
#inner.each { |itm| itm.send(meth, value) }
else
raise ArgumentError, "MArray: not an assignment"
end
end
end
We also need to add support for MArray in Array, so that the wrapping will take place. We'll call the method mas for "mass-assignment":
class Array
def mas
# Wrap MArray around self
MArray.new(self)
end
end
Usage is simple:
Blob = Struct.new(:dab)
arr = [Blob.new] * 3
arr.mas.dab = 123
arr
=> [#<struct Blob dab=123>, #<struct Blob dab=123>, #<struct Blob dab=123>]
Solution 2 - Create mass-assignment support directly into Array
This is a bit more "dangerous" since we directly modify method_missing in Array. It could create some strange side-effects (for example if method_missing has already been redefined by some other library, or you accidentally call a mass-assign while you didn't mean to).
It works by trying to detect assignments with plural words (words ending with s), and then triggering the mass-assignment:
class Array
def method_missing(meth, *args, &block)
# Check for plural assignment, and as an added safety check also
# see if all elements in the array support the assignment:
if meth.to_s =~ /^(\w+)s=$/ &&
self.all? { |itm| itm.respond_to?("#{$1}=") }
self.each { |itm| itm.send("#{$1}=", *args) }
else
super
end
end
end
Usage then becomes even shorter than with MArray:
Blob = Struct.new(:dab)
arr = [Blob.new] * 3
arr.dabs = 123
arr
=> [#<struct Blob dab=123>, #<struct Blob dab=123>, #<struct Blob dab=123>]

Resources