Ways to refactor named arguments with default values - ruby

I have a method with a lot of named arguments, some with default values:
def myClass
def initialize(a:, b:, c:, d:, e:, f:, g: nil, h: nil, i: nil)
...
end
end
The list is a little hard to look at and comprehend. I am looking for way to make this simpler.
Using a hash for args,
myClass.new(**args)
works, but I can't have both symbols with and without a value.
Is there a way to make this simpler?

You could try this
def myClass
def initialize(args)
[:a, :b, :c, :d, :e, :f].each do |a|
raise ArgumentError.new unless args.has_key?(a)
end
...
end
end
args is a hash object.

May be there are cases where a function needs such a large number of parameters, but normally this indicates that a function is doing too many things in one place.
Ok, if you want to do it, I would move it into a special private method:
class MyClass
def initialize(*args)
args = set_defaults(args)
end
private
def set_defaults(args)
# step 1: extract the options hash and check the keys,
# if a key doesn't exist so put it in with the default value
options = args.extract_options!
[g: :state, h: 'a name', i: 5].each do |key, value|
options[key] = value unless options.key?(key)
end
# step 2: check the other array elements
[:a, :b, :c, :d, :e, :f].each do |element|
raise ArgumentError.new unless args.include?(element)
end
# step 3: put them all together again
args << options
end
end
BTW: def className doesn't work. It's class ClassName. In addition please have a look at the beautiful ruby style guide - naming.

Related

Automatically generate 'attr_reader' for symbols

I have the following code where I set attr_reader and attr_writer manually.
class Pairs
attr_reader :pair, :asks, :bids, :isFrozen, :seq, :main_currency, :sub_currency
attr_writer :pair, :asks, :bids, :isFrozen, :seq
def initialize (key, args)
#pair = key
#main_currency, #sub_currency = key.split('_')
args.each {|k,v|
if numeric?(v) then v=v.to_f end
self.instance_variable_set("##{k}".to_sym, v)
}
end
private
def numeric?(string)
Float(string) != nil rescue false
end
end
Is there a way to automatically set them based on the keys of the arguments, like I'm automatically filling #k with v? Can I set attr_reader for each #k?
I suppose something like:
self.attr_reader("##{k}")
or even better for all objects of the class, something like:
Pairs << attr_reader("##{k}")
I am going to assume that you may be creating this with many keys specific to different Hash if this is the case then rather than clutter the individual instances with unneeded readers for non existent keys let's use the singleton_class for this.
So your final Pairs class could look something like
class Pairs
attr_reader :main_currency, :sub_currency
attr_accessor :pair, :asks, :bids, :isFrozen, :seq
def initialize (key, args)
#pair = key
#main_currency, #sub_currency = key.split('_')
args.each do |k,v|
singleton_class.send(:attr_reader,k)
instance_variable_set("##{k}", convert_numeric(v))
end
# Alternatively:
# args.each do |k,v|
# val = convert_numeric(v)
# define_singleton_method(k) {val}
# end
end
private
def convert_numeric(val)
Float(Rational(val)) rescue val
end
end
TL;DR
For Example: (using #mudasobwa's approach)
class C
def extend_self_with_reader name
self.class.send :attr_reader, name
end
def initialize *keys
keys.each(&method(:extend_self_with_reader))
end
end
This causes subsequent readers to clutter the instance and bleed across instances:
a = C.new(:a,:b)
a.a #=> nil
b = C.new
b.a #=> nil
c = C.new(:r)
c.a #=> nil
c.r #=> nil
a.methods.sort - Object.methods
#=> [:a, :b, :extend_self_with_reader, :r]
a.r #=> nil (hmmmmm)
Instead localize these readers buy using the singleton_class of the instance like:
class C
def initialize *keys
singleton_class.send(:attr_reader, *keys)
end
end
Then
a = C.new(:a,:b)
a.a #=> nil
b = C.new
b.a #=> NoMethodError: undefined method `a'
c = C.new(:r)
c.a #=> NoMethodError: undefined method `a'
c.r #=> nil
a.r #=> NoMethodError: undefined method `r'
a.methods.sort - Object.methods
#=> [:a,:b]
b.methods.sort - Object.methods
#=> []
Using the singleton_class localizes these readers to the instance of the object rather than bleeding them into the Class definition. If attr_reader is not a requirement then this would also be sufficient:
keys.each {|k| define_singleton_method(k) {}}
I doubt I understood the question, but from what I get you want to dynamically extend your class with attribute readers at runtime.
This method would do:
def extend_self_with_reader name
self.class.send :attr_reader, name
end
Test:
class C
def extend_self_with_reader name
self.class.send :attr_reader, name
end
def initialize *keys
puts keys.inspect
keys.each(&method(:extend_self_with_reader))
end
end
cc = C.new(*%i|a b c|)
cc.a #⇒ nil
Perhaps look into define_method.
I'm not 100% sure that I understand the problem, but check this out:
hash = { a: 1, b: 2, c: 3 }
hash.keys.each do |key|
define_method(key) do
hash[key]
end
end
There are now methods for a, b, and c:
a => 1
b => 2
c => 3
That essentially makes an attr_reader for all the keys in the hash. You could do something similar for an attr_writer.

Copy of object with attribute set in ruby

Is it possible to return create copy of object with an attribute set, in Ruby?
Of course, a method can be defined to do this -
class URI::Generic
def with_query(new_query)
ret = self.dup
ret.query = new_query
ret
end
end
But this might get a bit tedious to do with every attribute.
You can use options hash to pass multiple attribute-value pairs. Here is an illustrative example.
class Sample
attr_accessor :foo, :bar
def with(**options)
dup.tap do |copy|
options.each do |attr, value|
m = copy.method("#{attr}=") rescue nil
m.call(value) if m
end
end
end
end
obj1 = Sample.new
#=> #<Sample:0x000000029186e0>
obj2 = obj1.with(foo: "Hello")
#=> #<Sample:0x00000002918550 #foo="Hello">
obj3 = obj2.with(foo: "Hello", bar: "World")
#=> #<Sample:0x00000002918348 #foo="Hello", #bar="World">
# Options hash is optional - can be used to just dup the object as well
obj4 = obj3.with
#=> #<Sample:0x0000000293bf00 #foo="Hello", #bar="World">
PS: There may be few variations on how to implement options hash, however, essence of approach will be more or less same.

Appending dynamic value to ruby instance variable

I know this code doesn't look good at all , but i just want to explain my requirement. I want to know is there any good or alternative approach to it.
Actually, i want to create a new stack and whenever one stack has reached its capacity. I want to keep track of number of stacks created like #stack_1, #stack_2 ...by incrementing #number += 1 like #stack_#number. And for every stack, i want to maintain a #current_position pointer which is specific to every stack like #stack_2 has #current_position_2. So i want to create dynamic instance variables.
Example:
def initialize
#number = 1
#stack+"#{#number}" = Array.new(10)
#current_position_"#{#number}" = 0
end
Output should be something like #stack1 = Array.new(10).
Lets say if i increment value of #number += 1, it should look like #stack2 = Array.new(10)
Instead of array I suggest you to use Hash Map
#stack = Hash.new
#stack[#number] = <Your Array>
Be Careful if the #number is same your array will be replaced..
For more information about hash maps http://www.ruby-doc.org/core-1.9.3/Hash.html
You can do it like this:
instance_variable_set("#stack#{#number}", Array.new(10, :a))
#stack1
#=> [:a, :a, :a, :a, :a, :a, :a, :a, :a, :a]
instance_variable_set("#stack#{#number+1}", Array.new(10, :b))
#stack2
#=> [:b, :b, :b, :b, :b, :b, :b, :b, :b, :b]
instance_variable_set("#current_position_#{#number}", 0)
#current_position_1
#=> 0
Instead of creating instance variables to track a stack's state from the outside, you could create a Stack class that tracks its state internally. Here's a very simple one:
class StackOverflow < StandardError; end
class Stack
def initialize
#stack = []
end
def position
#stack.size
end
def full?
position == 2 # small size for demonstration purposes
end
def push(obj)
raise StackOverflow if full?
#stack << obj
end
end
stack = Stack.new
stack.push "foo"
stack.full? #=> false
stack.push "bar"
stack.full? #=> true
stack.push "baz" #=> StackOverflow
Having a working stack, you can build something like a StackGroup to handle multiple stacks:
class StackGroup
attr_reader :stacks
def initialize
#stacks = [Stack.new]
end
def push(obj)
#stacks << Stack.new if #stacks.last.full?
stacks.last.push(obj)
end
end
stack_group = StackGroup.new
stack_group.push "foo"
stack_group.push "bar"
stack_group.stacks.size #=> 1
stack_group.push "baz" # no error here
stack_group.stacks.size #=> 2
stack_group.stacks
#=> [#<Stack:0x007f9d8b886b18 #stack=["foo", "bar"]>,
# #<Stack:0x007f9d8b886a50 #stack=["baz"]>]

iterating over values in a block

I use blocks to create values like so
some_block = BlockClass.new {|b|
b.one = 1
b.two = 2
b.three = 3
}
Here is BlockClass
class BlockClass
attr_accessor :one
attr_accessor :two
attr_accessor :three
def initialize
yield self if block_given?
end
end
I need a way to iterate over some_block, and print all the value in the block without having to do
puts some_block.one
puts some_block.two
puts some_block.three
Is there a way to iterate over the values in the block?
First of all, the b parameter in the block is nil, so you will get a
NoMethodError: undefined method `one=' for nil:NilClass`
To fix this, you can change yield if block_given? to yield(self) if block_given?, which will pass self as the first parameter to the block.
If you want the b.one = ..., b.two = ... syntax, you should use an OpenStruct:
require 'ostruct'
class BlockClass < OpenStruct
def initialize
super
yield(self) if block_given?
end
end
You can get a dump of the internal Hash by calling marshal_dump:
some_block = BlockClass.new {|b|
b.one = 1
b.two = 2
b.three = 3
}
some_block.marshal_dump # => {:one=>1, :two=>2, :three=>3}
You can then iterate over the values:
some_block.marshal_dump.each_pair do |k, v|
puts "the value of #{k} is #{v}"
end
Your block takes 1 parameter, b, but your yield statement doesn't pass anything in. Perhaps you mean, yield self if block_given??
Also, if you want to "iterate", you'll need an enumerable collection of something, like an Array or Hash. As is, one, two, and three are totally unrelated accessors to your BlockClass.
You could iterate over all methods of BlockClass:
(some_block.methods).each do |method_name|
puts some_block.send(method_name)
end
But that doesn't sound like what you're looking for. Perhaps Initialize a Ruby class from an arbitrary hash, but only keys with matching accessors might help?

Can I refer to an object's attribute by string in Ruby?

If I have a ruby class called Node:
class Node
attr_accessor :a, :b, :c
attr_reader :key
def initialize(key)
#key = key
end
def [](letter)
if letter == 'a'
return self.a
elsif letter == 'b'
return self.b
elsif letter == 'c'
return self.c
end
end
end
How can I optimize def [](letter) so I won't have repetitive code? More specifically, how can I access an attribute of an object (that is a ruby symbol :a, :b, or :c) by using a corresponding string?
You can use send, which invokes a method dynamically on the caller, in this case self:
class Node
def [](key)
key = key.to_sym
send(key) if respond_to?(key)
end
end
Note that we check that self has the appropriate method before calling it. This avoids getting a NoMethodError and instead results in node_instance[:banana] returning nil, which is appropriate given the interface.
By the way, if this is the majority of the behavior of your Node class, you may simply want to use an OpenStruct:
require 'ostruct'
node_instance = OpenStruct.new(a: 'Apple', b: 'Banana')
node_instance.a #=> 'Apple'
node_instance['b'] #=> 'Banana'
node_instance.c = 'Chocolate'
node_instance[:c] #=> 'Chocolate'

Resources