Creating a Setter Method That Takes Extra Arguments in Ruby

Creating a setter method that takes extra arguments in Ruby

It doesn't work because the parser doesn't allow it. An equals sign is allowed in expressions of the form identifier = expression, expression.identifier = expression (where identifier is \w+), expression[arguments] = expression and expression.[]= arguments and as part of a string or symbol or character literal (?=). That's it.

gen.send(:allow=, 'a', 1, false) would work, but at that point you could as well just give the method a name that doesn't include a =.

Setter method (assignment) with multiple arguments

Due to the syntax sugar of methods whose names end in=, the only way that you can actually pass multiple parameters to the method is to bypass the syntax sugar and use send

h.send(:strategy=, :mystrategy, :backward )

…in which case you might as well just use a normal method with better names:

h.set_strategy :mystrategy, :backward

However, you could rewrite your method to automatically un-array the values if you knew that an array is never legal for the parameter:

def strategy=( value )
if value.is_a?( Array )
@strategy << value.first
@strategy.direction = value.last
else
@strategy = value
end
end

This seems like a gross hack to me, however. I would use a non-assigment method name with multiple arguments if you need them.


An alternative suggestion: if the only directions are :forward and :backward what about:

def forward_strategy=( name )
@strategy << name
@strategy.direction = :forward
end

def reverse_strategy=( name )
@strategy << name
@strategy.direction = :backward
end

What can I do with a setter method that takes no arguments

You can't use it as a setter, and it's not even very useful as a method in general. The only way to even make that method run is to use send or create a Method instance, because the normal message-send syntax doesn't allow the message to include a "=" character.

So you technically could have code like this:

class Foo
def bar=()
puts "Hi, mom!"
end
end

foo = Foo.new
foo.send(:bar=)

and it would print "Hi, mom!" as expected.

But can you do anything with it? Well, I guess you could brag to all your friends about your nigh-uncallable setter that doesn't set anything. Beyond that, no, it's really not useful for anything.

Alias a bracket setter with extra parameter (index and value)

Given this class

class GameVariables
def []=(key, value)
# do something
end
end

vars = GameVariables.new

you can call the setter in different, equivalent ways. The most common and idiomatic one is this

vars[:abc] = 123

This however is actually using syntactic sugar built into Ruby. In fact, this call actually resolves to this:

vars.[]=(:abc, 123)

This works similar for all the operators like +, -, == and other types of array/hash accessors. In Ruby, the whole concept of operators is just syntactic sugar that makes the Ruby parser automatically resolve certain calling constructs (like the hash setter) to their actual method calls. This however only works for some specifically named method names, one of which being []=.

When you want to use the functionality of your original []= method using another name, you can`t rely on the parser to resolve the syntactic sugar anymore. Thus, your call

foo[variable_id] = value

actually resolves to this:

foo()[variable_id] = value

which obviously passes to few arguments to the method. To actually be able to properly call the foo method, you can use this instead:

foo(variable_id, value)

This uses the plain old method calling syntax without any need for the parser applying any syntactic sugar.

Ruby, how to overwrite the setter (=) method to allow 2 parameters?

As @eugen correctly says, you cannot pass two arguments to a setter like

self.my_vector = 1, 2

The nearest thing you can achieve is to unpack the argument using pattern matching:

def myvector=(arg)
arg => [x, y]
@my_vector = Vector2d.new(x, y)
end

foo.my_vector = [1, 2]

An alternative is to define a simple helper method v so you can write

foo.my_vector = v 1, 2

Assigning multiple values to setter at once: self.x = (y, z) results in a syntax error

You indeed can't pass more than one argument to a method that ends in =. A setter method doesn't need to end in =, though, naturally: you can just do set_title_and_author(title, author).

Another alternative would be to have the method take an array:

def set_title_and_author= (title_and_author)
@title, @author = title_and_author
end

#...

book.set_title_and_author= ["Ender's Game", "Orson Scott Card"]

If you do the latter, stylistically I'd recommend removing the set and just calling the method title_and_author=. set is redundant with =.

Ruby get and set in one method

The trickier case is if you want to set duration to nil. I can think of two ways of doing this

def duration(*args)
@duration = args.first unless args.empty?
@duration
end

Allow people to pass any number of args and decide what to do based on the number. You could also raise an exception if more than one argument is passed.

Another way is

def duration(value = (getter=true;nil))
@duration = value unless getter
@duration
end

This exploits default arguments a little: they can be pretty much any expression.

When called with no arguments getter is set to true, but when an argument is supplied (even if it is nil) the default value is not evaluated. Because of how local variable scope works getter ends up nil.

Possibly a little too clever, but the method body itself is cleaner.

How can I create a setter method for a new object with default values?

Make use of default keyword parameters:

def default_product(
title: "default title",
description: "default description",
price: 1,
image_url: 'default_url.png')
Product.new(
title: title,
description: description,
price: price,
image_url: image_url)
end

And call it like: default_product(title: "Another title").


Sidenote: the proper approach would be probably to use FactoryBot.



Related Topics



Leave a reply



Submit