How to optimize Ruby method - ruby

I have a ruby method
def get_status(creds)
client = create_client(creds)
status = client.account_status
client.close_session
status
end
Usually, I optimize this kind of code by tap or yield_self, but here I can't find a nice way to optimize it.
The only solution I have come up:
def get_status(creds)
create_client(creds).yeild_self do |client|
[client, client.account_status]
end.yield_self do |client, status|
client.close_session
status
end
end
But it doesn't better than the original solution, is it?

One could write the following.
class Client
def account_status
"Overdrawn!"
end
def close_session
puts "It's closed"
end
end
def create_client(creds)
Client.new
end
def get_status(creds)
begin
client = create_client(creds)
client.account_status
ensure
client.close_session if client
end
end
get_status("Anything")
It's closed
#=> "Overdrawn!"
Do I prefer this to #1 in the question? No.
Do I prefer this to #2 in the question? Yes!
Do I prefer this to #max's answer? No.
I understand a finalizer could be created using the class method ObjectSpace::define_finalizer.
class Client
def initialize
ObjectSpace.define_finalizer(self, proc { puts "It's finalized!" })
end
def account_status
"Overdrawn!"
end
end
def create_client(creds)
Client.new
end
def get_status(creds)
create_client(creds).account_status
end
get_status("Anything")
#=> "Overdrawn!"
exit
It's finalized!
One must be careful when creating finalizers, as explained Here. I understand a technique sometimes used is to have finalizer's proc reference class-level objects. See, for example, this article, #Amadan's comments below and #Matt's comments on the question. I am not advocating the use of a finalizer. I merely thought readers unfamiliar with finalizers (as I was before writing this) would find this useful.

Let's list the goal of the function:
Open connection
Read value (and return it)
Close connection
I would consider this a "temporary connection", and that leads me to think it could be refactored to a separate method.
Reasoning: The get_status method is concerned with getting the status from a connection - it doesn't have to handle the details of actually closing/opening the connection itself.
def open_temporary_connection(creds, &block)
client = create_client(creds)
result = block.call(client)
client.close_session
result
end
def get_status(creds)
open_temporary_connection(creds, &:account_status)
end
Also, I should mention, I think yield_self is a bit of a trap. Unless you're dead set on making all of your code into a single expression, it makes the code look awkward without offering a lot of benefit.

I like your first version because it is short, easy to read, and easy to understand. I would not change it.
Nevertheless, an alternative version using tap might look like this:
def get_status(creds)
client = create_client(creds)
client.account_status.tap { client.close_session }
end

Related

NilCheck fix on safe navigation operator (&.)

This simple method on a class just run the status method using the safe navigation operator.
def current_status
account&.status
end
But reek report this warning:
MyClass#current_status performs a nil-check [https://github.com/troessner/reek/blob/master/docs/Nil-Check.md]
How can I properly write methods like this to avoid Nil Check?
I've also verified this post from thoughtbot but it seem like "too much" for just a safe navigation operator.
Ruby 2.3.1
The advice from "Example 4" in the linked post is verbose but pretty good :
class MyClass
def initialize(with_account = nil)
#account = Account.new if with_account
end
def current_status
account.status
end
def account
#account || NilAccount.new
end
end
class Account
def status
"Up!"
end
end
class NilAccount
def status
"Down!"
end
end
puts MyClass.new(:with_account).current_status
#=> "Up!"
puts MyClass.new.current_status
#=> "Down!"
If it's "too much" for you, account&.status might be just fine.
Whatever you do : you'll need to test your code as much as possible!
well, tell-dont-ask looks pretty good, but Example 4 looks like an overkill to resolve this specific case.
#andredurao I think, we can use this workaround to pass checks, for some reason reek is fine with it:
def current_status
return unless account
account.status
end

ruby refactor with block

I want to refactor this code snippet as we can see a lot of duplication going around here. Can we use block refactoring to refactor the code given below.
module Jobs
SendTrackEvent = Struct.new(:user_id, :event, :properties) do
include A
include B
def perform
#tracker ||= Tracker.new(Settings.key)
#tracker.track(user_id, event, properties)
end
end
SendAliasEvent = Struct.new(:new_distinct_id, :original_anonymous_id) do
include A
include B
def perform
#tracker ||= Tracker.new(Settings.key)
#tracker.track(new_distinct_id, original_anonymous_id)
end
end
end
Thanks
This answer has been deleted for a while to see if it was the OP's sloppyness that made the code complicated; otherwise, a different solution would have been necessary. The OP has FINALLY (forty minutes after being pointed out, despite being online) made it clear, and fixed the question, so this answer is back again.
module Jobs
def self.create_struct(*args) do
Struct.new(*args) do
include A
include B
def perform
#tracker ||= Tracker.new(Settings.key)
#tracker.track(*args.map{|sym| send(sym)})
end
end
end
SendTrackEvent = create_struct(:user_id, :event, :properties)
SendAliasEvent = create_struct(:new_distinct_id, :original_anonymous_id)
end

User interrupt in Ruby infinite loop (multiple classes)?

I found another question very similar to mine with a solution that worked for me when I wrote it all in one simple script. I even wrote a second simple example sort of simulating what I'm trying to do, and it seemed to still work.
My simulation was:
class A
def looper(&block)
Thread.new do
loop do
exit if gets.chomp == 'q'
end
end
loop do
block.call
end
end
end
class B < A
def looper
super do
puts 'howddyyyy from B'
end
end
end
This works fine, exiting when you press q<Enter>. However, when I tried to implement this into my actual project, it fails to work. I'll post the code from the method in question in the child class, as the parent class is literally exactly the same as the example above.
def looper
super do
if obj = Object.first(:process_status => STATUS_UNPROCESSED)
puts "[Object ##{obj.id}] Processing..."
puts "-" * 60
obj.set_failed
if #obj.process(obj)
obj.set_processed
end
puts "-" * 60
puts "[Object ##{obj.id}] Finished!"
puts
puts
else
sleep 10
end
end
end
So, for some reason, this doesn't work. I put a puts into the new Thread (listening for q), and it seems to output the puts before every loop of block.call. Maybe it just isn't able to get the key, by which I mean, maybe the timeframe in which you have to enter q<Enter> is way too small? I'm not sure, which is why I'm asking some advice here. My only other guess is that it has something to do with the methods called within this method (process, or possible the Sequel calls to the database) blocking the other thread(s)?
I'm new to threading, so I have no clue.
Okay, everybody. I feel a little stupid for typing all that up, as I came to a solution not five minutes later (and one I had overlooked here on Stack Overflow).
For anyone facing a similar issue in the future, this is what I ended up doing (in the parent class):
def looper(&block)
interrupted = false
trap("INT") { interrupted = true }
until interrupted do
block.call
end
exit
end
This manages to achieve what I was essentially trying to do.
Thanks for reading!

How to return a value or conditionally raise an error in Ruby?

I am calling an API from a Rails model and I would like to raise an error if the API returns on non-200 code. Otherwise I want to cache/lazy-load the data. This is my method:
def data
#data ||= SNL.get_list(name)
raise StandardError, #data.inspect unless #data.success?
#data
end
This works but I was wondering if I can accomplish this in one line. I tried using the and operator combined with an unless but couldn't get it to work.
Update: I have accepted tokland's answer because I asked for one line and he/she provided two very good solutions. In the end I am actually going to use
def data
#data ||= SNL.get_list(name)
#data.success? ? #data : (raise StandardError, #data.inspect)
end
for readability. I just hated having a third line just to return #data, since an exception will rarely be raised. I feel odiszapc's answer is a the best compromise of brevity and readability. Thanks everyone.
I wouldn't strain to write a one-liner, but you can use tap if you absolutely must:
def data
(#data ||= SNL.get_list(name)).tap { |d| d.success? or raise StandardError.new(d.inspect) }
end
Also with short-circuited logic:
def data
(#data ||= SNL.get_list(name)).success? && #data or
raise StandardError.new(#data.inspect) }
end
You can just use a terneray operator. However, I think its really important to keep your code as readable as possible. And generally in my experience, code that spreads a bit too much horizontally is generally a bit tough to follow.
There is one thing you need to be sure of. If SNL.get_list(name) returns nil and you're trying to use the and operator along with it, it won't work.
This issue has happened with me numerous times. A sample example:
nil and puts 'hello'
try this in your irb. It won't work. This issue has occurred with me numerous times.
Maybe
def data
#data ||= SNL.get_list(name)
#data.success? ? #data : (raise StandardError, #data.inspect)
end
Or I'm not sure, something like:
def data
(#data ||= SNL.get_list(name)).success? ? #data : (raise StandardError, #data.inspect)
end
try
def data
(#data ||= SNL.get_list(name)).success? ? #data : raise(StandardError, #data.inspect)
end
Again #Sohaib's point is valid this is not quite readable! and not a rubbish's way, lot of parentheses

A ruby method to replace "="

I want to eliminate "=" sign for a particular reason. It might looks like this:
cat_that_has_name("Kelly").as(:kelly)
kelly.do_something
The "as" method here is used to generate a method "kelly" that reference my cat. Could anyone help me with this?
Any suggestions will be appreciated.
Update:
Jorg was right, I've add a simple test to demonstrate my intention:
require "test/unit"
class AsTest < Test::Unit::TestCase
def setup
#cats = ["Kelly", "Tommy"]
end
def teardown
end
def test_as
kelly1 = get_cat("Kelly")
get_cat("Kelly").as(:kelly2)
assert_equal(kelly1.object_id, kelly2.object_id)
end
private
def get_cat(name)
#cats.each do |cat|
if cat.to_s==name
return cat
end
end
return nil
end
end
It's kind of hard to figure out what you actually want. If you want some sensible answers, you will have to provide a complete code example of what you want to achieve (for example, the code you posted is missing definitions for the cat_that_has_name and so_something methods). You will also need to post a complete specification of what exactly you expect the as method to do, with usage examples and ideally also with a testsuite. After all, how do we know if our answer is correct if you haven't defined what "correct" means?
The best I could decipher from your cryptic question is something like this:
class Object
def as(name)
s = self
Object.send(:define_method, name) { s }
Object.send(:private, name)
end
end
But there is no way of knowing whether this works, because if I try to run your code example, I get a NoMethodError for cat_that_has_name and another NoMethodError for so_something.
Note also that your question is self-inconsistent: in your subject line you ask about a method to replace = (i.e. creating variables) but in your question you talk about creating methods, which would mean that you are looking for a replacement for def and not for =. Again, it would be much easier to answer correctly if there were a testsuite.

Resources