How can I write better code for passing key-value arguments? - ruby

I want to write code for Ruby in a more Ruby-like style and ran into a problem when working with argument passing.
I have to see if ABC is nil or not. If ABC is nil, I would pass another symbol into dosomething, if not I would pass another type of hash value to compute.
Since Ruby is not like Java, it can pass a different type of argument (different keys).
How can I make the following code more beautiful?
Merging do_manything, do_otherthings, do_manythings_again into a single function is not the answer I because I would call dosomething in many places in my code:
if ABC.nil?
Apple.dosomething (:foo => DEF) { |a|
a.do_manything
a.do_otherthings
a.do_manythings_again
}
else
Apple.dosomething (:bar => ABC) { |a|
a.do_manything
a.do_otherthings
a.do_manythings_again
}
end

Using the ternary operator.
Apple.dosomething (ABC.nil? ? {foo:DEF} : {bar:ABC}) do |a|
a.do_manything
a.do_otherthings
a.do_manythings_again
end
Here is the format
condition ? return_if_true : return_if_false

You can either switch the hash you send:
opts = ABC.nil? ? {foo:DEF} : {bar:ABC}
Apple.dosomething(opts) do |a|
do_many_things
do_other_things
do_many_things_again
end
...or you can pass a lambda as the block:
stuff_to_do = ->(a) do
do_many_things
do_other_things
do_many_things_again
end
if ABC.nil?
Apple.dosomething(foo:DEF,&stuff_to_do)
else
Apple.dosomething(bar:ABC,&stuff_to_do)
end

You could do this:
options = if ABC.nil? then { foo: DEF } else { bar: ABC } end
Apple.do_something options do |apple|
apple.instance_eval do
do_many_things
do_other_things
do_many_things_again
end
end
By convention, words in names and identifiers are separated by underscores (_) and do/end is used for multiple-line blocks.
Also, I believe this question belongs on Code Review.

Related

What does this ruby snippet do?

As a ruby newbie I am trying to understand a snippet of code in our baseline. Could someone please do that for me ? The snippet appears below
%w{word1 word2}.each { |att| define_method(att.to_sym) { return nil }}
In the context where this line will be run, two methods will be defined
def word1
return nil
end
def word2
return nil
end
For example
class MyClass
%w{word1 word2}.each { |att| define_method(att.to_sym) { return nil }}
end
After my_class.rb file will be loaded you will be able to consume generated methods
test = MyClass.new
test.word1
# or
test.word2
Like jdv said in the comments, for tutorials you might be better of on other websites. Here are all references needed to understand the piece of code provided:
Percent strings, used in %w{word1 word2}
Percent Strings
Besides %(...) which creates a String, the % may
create other types of object. As with strings, an uppercase
letter allows interpolation and escaped characters while a
lowercase letter disables them.
These are the types of percent strings in ruby:
%i: Array of Symbols
%q: String
%r: Regular Expression
%s: Symbol
%w: Array of Strings
%x: Backtick (capture subshell result)
For the two array forms of percent string, if you wish to
include a space in one of the array entries you must escape
it with a “\” character:
%w[one one-hundred\ one]
#=> ["one", "one-hundred one"]
If you are using “(”, “[”, “{”, “<” you must close it with
“)”, “]”, “}”, “>” respectively. You may use most other
non-alphanumeric characters for percent string delimiters
such as “%”, “|”, “^”, etc.
Array#each
each {|item| block} → ary
each → Enumerator
Calls the given block once for each element in self,
passing that element as a parameter. Returns the array
itself.
If no block is given, an Enumerator is returned.
a = [ "a", "b", "c" ]
a.each {|x| print x, " -- " }
produces:
a -- b -- c --
Module#define_method
define_method(symbol, method) → symbol
define_method(symbol) { block } → symbol
Defines an instance method in the receiver. The method
parameter can be a Proc, a Method or an UnboundMethod
object. If a block is specified, it is used as the method
body. This block is evaluated using instance_eval.
class A
def fred
puts "In Fred"
end
def create_method(name, &block)
self.class.define_method(name, &block)
end
define_method(:wilma) { puts "Charge it!" }
end
class B < A
define_method(:barney, instance_method(:fred))
end
a = B.new
a.barney
a.wilma
a.create_method(:betty) { p self }
a.betty
produces:
In Fred
Charge it!
#<B:0x401b39e8>
String#to_sym
to_sym → symbol
Returns the Symbol corresponding to str, creating the
symbol if it did not previously exist. See Symbol#id2name.
"Koala".intern #=> :Koala
s = 'cat'.to_sym #=> :cat
s == :cat #=> true
s = '#cat'.to_sym #=> :#cat
s == :#cat #=> true
This can also be used to create symbols that cannot be
represented using the :xxx notation.
'cat and dog'.to_sym #=> :"cat and dog"
%w{word1 word2} = creating an array that looks like this ['word1', 'word2']
.each = iterating through each value in the array
{} = this is a code block each value in the array will be run through this block
|attr| = block parameter. each value in the array will be placed here
define_method = define a method from the argument
(att.to_sym) = the name of the new method. this will be word1 and then word2
{ return nil } = the body of the new method
So what is happening is you are defining two new methods. One method called word1 and another called word2. Each of these methods will have a body of return nil. They will look like this:
def word1
return nil
end
def word2
return nil
end

How do I transform a Ruby block and apply it as an argument to another block?

I'm reducing the verbosity on a lot of related specs with a small shortcut method I wrote:
def association_spec_for(kind, field)
it { expect(subject).to send(kind, field) }
end
This gets used like this:
describe Student do
association_spec_for :have_many, :courses
association_spec_for :have_one, :transcript
end
Now I'd like to expand the way association_spec_for works, so that I can do this while still leaving the original use cases intact:
association_spec_for(:foo, :bar) do |a|
a.baz(:blerp).bloop(:bleep => :blarg)
end
and have it turn into this:
it { expect(subject).to send(:foo, :bar).baz(:blerp).bloop(:bleep => :blarg) }
# |----------------------------------|
# This part came from the block
# that was passed to a_s_f.
What's the best way to make that happen?
def association_spec_for(kind, field, &pr)
it{expect(subject).to pr ? pr.call(send(kind, field)) : send(kind, field)}
end

Issues iterating over a hash in Ruby

What I'd like to do is pass in a hash of hashes that looks something like this:
input = {
"configVersion" => "someVers",
"box" =>
{
"primary" => {
"ip" => "192.168.1.1",
"host" => "something"
},
"api" => {
"live" => "livekey",
"test" => "testkey"
}
}
}
then iterate over it, continuing if the value is another hash, and generating output with it. The result should be something like this:
configVersion = "someVers"
box.primary.ip = "192.168.1.1"
box.primary.host = "something"
and so on...
I know how to crawl through and continue if the value is a hash, but I'm unsure how to concatenate the whole thing together and pass the value back up. Here is my code:
def crawl(input)
input.each do |k,v|
case v
when Hash
out < "#{k}."
crawl(v)
else
out < " = '#{v}';"
end
end
end
My problem is: where to define out and how to return it all back. I'm very new to Ruby.
You can pass strings between multiple calls of the recursive method and use them like accumulators.
This method uses an ancestors string to build up your dot-notation string of keys, and an output str that collects the output and returns it at the end of the method. The str is passed through every call; the chain variable is a modified version of the ancestor string that changes from call to call:
def hash_to_string(hash, ancestors = "", str = "")
hash.each do |key, value|
chain = ancestors.empty? ? key : "#{ancestors}.#{key}"
if value.is_a? Hash
hash_to_string(value, chain, str)
else
str << "#{chain} = \"#{value}\"\n"
end
end
str
end
hash_to_string input
(This assumes you want your output to be a string formatted as you've shown above)
This blog post has a decent solution for the recursion and offers a slightly better alternative using the method_missing method available in Ruby.
In general, your recursion is correct, you just want to be doing something different instead of concatenating the output to out.

Ruby equivalent for Python's for / else

I've always been searching for something like Python's while / else struct in Ruby to improve my code.
That means that the loop is executed and if the condition in the loop hasn't been true any time, then it returns the value in the else statement.
In ruby, I can do like this :
if #items.empty?
"Empty"
else
#items.each do |item|
item
end
end
So is there a way to improve this ?
Thank you in advance.
Remember that the iterator block returns what you put into it, which can be tested for further use.
if arr.each do |item|
item.some_action(some_arg)
end.empty?
else_condition_here
end
Hm, you could write it as a ternary:
#items.empty? ? 'Empty' : #items.each { |item| item }
You may want to do something more useful in your block though, since each is executed for its side effects and returns the original receiver.
Update as per your comment: I guess the closest you could get is something like
unless #items.empty?
#items.each { |item| p item }
else
'Empty'
end
Since we are in Ruby, let's have fun. Ruby has powerful case construct, which could be used such as this:
case items
when -:empty? then "Empty"
else items.each { |member|
# do something with each collection member
}
end
But to make the above code work, we have to modify the native class Symbol first. Modification of native classes is Ruby specialty. This needs to be done only once, typically in a library (gem), and it helps you ever after. In this case, the modification will be:
class Symbol
def -#
Object.new
.define_singleton_method :=== do |o| o.send self end
end
end
This code overloads the unary minus (-) operator of Symbol class in such way, that saying -:sym returns a new empty object monkey patched with :=== method, that is used behind the scenes by the case statement.
A more or less functional way:
empty_action = { true => proc{ "Empty"},
false => proc{ |arr| arr.each { |item| item }} }
empty_action[#items.empty?][#items]

Ruby: How to chain multiple method calls together with "send"

There has to be a built in way of doing this, right?
class Object
def send_chain(arr)
o=self
arr.each{|a| o=o.send(a) }
return o
end
end
I just ran across this and it really begs for inject:
def send_chain(arr)
arr.inject(self) {|o, a| o.send(a) }
end
Building upon previous answers, in case you need to pass arguments to each method, you can use this:
def send_chain(arr)
Array(arr).inject(self) { |o, a| o.send(*a) }
end
You can then use the method like this:
arr = [:to_i, [:+, 4], :to_s, [:*, 3]]
'1'.send_chain(arr) # => "555"
This method accepts single arguments as well.
No, there isn't a built in way to do this. What you did is simple and concise enough, not to mention dangerous. Be careful when using it.
On another thought, this can be extended to accept arguments as well:
class Object
def send_chain(*args)
o=self
args.each do |arg|
case arg
when Symbol, String
o = o.send arg # send single symbol or string without arguments
when Array
o = o.send *arg # smash the inner array into method name + arguments
else
raise ArgumentError
end
end
return o
end
end
this would let you pass a method name with its arguments in an array, like;
test = MyObject.new
test.send_chain :a_method, [:a_method_with_args, an_argument, another_argument], :another_method
How about this versatile solution without polluting the Object class:
def chain_try(arr)
[arr].flatten.inject(self_or_instance, :try)
end
or
def chain_send(arr)
[arr].flatten.inject(self_or_instance, :send)
end
This way it can take a Symbol, a String or an Array with a mix of both even.🤔
example usage:
chain_send([:method1, 'method2', :method3])
chain_send(:one_method)
chain_send('one_method')

Resources