How to decouple functionality and logging in a ruby method - ruby-on-rails

I like to log a lot. In my Rails app I have a lot of methods like:
def my_method(argument1:, argument2:)
logger.info "Starting my_method with arguments: #{argument1} and #{argument2}"
result = argument1 + argument2
logger.info "Finished my_method with result: #{result}"
end
How to decouple the functionality and the logging of the methods?.
Ideally the result would look something like this (borrowing the callback concept from Rails just as an example):
before_method: :my_method_log_start, only: :my_method
after_method: :my_method_log_end, only: :my_method
def my_method(argument1:, argument2:)
result = argument1 + argument2
end
private
def my_method_log_start
logger.info "Starting my_method with arguments: #{argument1} and #{argument2}"
end
def my_method_log_end
logger.info "Finished my_method with result: #{result}"
end
I know this is less efficient in terms of lines of code, it is more readable (in my opinion).
I have read about Aspect Orient Programming and some of their gems like Aquarius, but looks like an overkill to add a new paradigm just for logging.

I think Avdi Grimm has a good explanation of the technique you could use. The idea is to extract logging (or anything else) to the listener class and publish events to that listener, basic example would be
class Task
# ...
def add_listener(listener)
(#listeners ||= []) << listener
end
# ...
def notify_listeners(event_name, *args)
#listeners && #listeners.each do |listener|
if listener.respond_to?(event_name)
listener.public_send(event_name, self, *args)
end
end
end
end
and do sth like
task = Task.new
task.add_lestener(YourLoggerClass.new)
task.notify_listeners(:start_logging)
task.notify_listeners(:end_logging)

If this is only for local debugging, it is the good use case for TracePoint class. Here is the code:
tp1 = TracePoint.new do |tp|
if tp.event == :call
method = tp.defined_class.method(tp.method_id)
arguments = method.parameters.map do |param|
"#{param[1]}: #{tp.binding.local_variable_get(param[1])}"
end.join(", ")
puts "Starting #{tp.method_id} with arguments #{arguments}"
elsif tp.event.to_s == "return"
puts "Finished #{tp.method_id} with result: #{tp.return_value}"
end
end
tp1.enable
def my_method1(a, b)
a + b
end
puts my_method1(2, 3)
I recommend reading the documentation for this class, it has really nice features. Of course you need to polish this code a little bit to handle some edge cases. You can add some filter to only invoke tracing block for methods that you care about. Or you can enable/disable this based on some parts of the code.

You can call method by it's name, or turn it to proc and pass to another method. So you can write something like that:
def foo(a, b)
a + b
end
def call_with_logging(method_name, *args)
args_as_string = args.map(&:to_s).join(' ')
puts "Starting my_method with arguments #{args_as_string}"
result = Object.send(method_name, *args)
puts "Finished my_method with result: #{result}"
end
call_with_logging :foo, 1, 2

Related

RSpec "change all" matcher

I've got some job that updates records, and I want something like:
it 'updates each record' do
expect {
described_class.perform_now
}.to(change_all_of{
Record.pluck(:updated_at)
})
end
Only I can't find anything that looks like how to accomplish this, or what I can recognize as the docs for how to write a custom matcher.
The issue is that change, on an array, will return true if any element has changed; I only want to return true if every element has changed.
Can anyone point me either at whatever I missed that would let me do this, OR, whatever docs/info I need to write my own change matcher?
Alright, thanks to this answer on another question, and staring at the actual code, here's what I've got -
module RSpec
module Matchers
def change_all &block
BuiltIn::ChangeAll.new(nil, nil, &block)
end
module BuiltIn
class ChangeAll < Change
def initialize receiver=nil, message=nil, &block
#change_details = ChangeAllDetails.new(receiver, message, &block)
end
def failure_message
"expected all elements to change, but " + (
#change_details.actual_after & #change_details.actual_before
).collect do |unchanged|
"before[#{#change_details.actual_before.find_index(unchanged)}] " \
"after[#{#change_details.actual_after.find_index(unchanged)}] " \
"#{unchanged}"
end.join(",") + " remained the same"
end
end
class ChangeAllDetails < ChangeDetails
attr_accessor :actual_before
def changed?
!(#actual_after & #actual_before).any?
end
end
end
end
end
Suggestions welcome!

Many very similar functions, spaghetti code fix?

I have approx 11 functions that look like this:
def pending_acceptance(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
pending_acceptance?; collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
def pending_start(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
pending_start?; collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
The iteration is always the same, but next unless conditions are different. In case you wonder: it's next unless and ; in it because RuboCop was complaining about it. Is there a solution to implement it better? I hate this spaghetti code. Something like passing the condition into "iterate_it" function or so...
edit: Cannot just pass another parameter because the conditions are double sometimes:
def picked_up(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless
order_fulfillment.handed_over_late? && order_fulfillment.
fulfillment_time_calculator.pending_handover?
collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
edit2: One question yet: how could I slice a symbol, to get a user role from a status? Something like:
:deliverer_started => :deliverer or 'deliverer'?
You can pass another parameter when you use that parameter to decide what condition to check. Just store all possible conditions as lambdas in a hash:
FULFILLMENT_ACTIONS = {
pending_acceptance: lambda { |fulfillment| fulfillment.fulfillment_time_calculator.pending_acceptance? },
pending_start: lambda { |fulfillment| fulfillment.fulfillment_time_calculator.pending_acceptance? },
picked_up: lambda { |fulfillment| fulfillment.handed_over_late? && fulfillment.fulfillment_time_calculator.pending_handover? }
}
def process_fulfillments(type, order_fulfillments)
condition = FULFILLMENT_ACTIONS.fetch(type)
order_fulfillments.each do |order_fulfillment|
next unless condition.call(order_fulfillment)
collect_fulfillments(order_fulfillment.status, order_fulfillment)
end
end
To be called like:
process_fulfillments(:pending_acceptance, order_fulfillments)
process_fulfillments(:pending_start, order_fulfillments)
process_fulfillments(:picked_up, order_fulfillments)
you can make array of strings
arr = ['acceptance','start', ...]
in next step:
arr.each do |method|
define_method ( 'pending_#{method}'.to_sym ) do |order_fulfillments|
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
send('pending_#{method}?'); collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
end
for more information about define_method
While next is handy it comes late(r) in the code and is thus a bit more difficult to grasp. I would first select on the list, then do the action. (Note that this is only possible if your 'check' does not have side effects like in order_fullfillment.send_email_and_return_false_if_fails).
So if tests can be complex I would start the refactoring by expressing the selection criteria and then pulling out the processing of these items (wich also matches more the method names you have given), somewhere in the middle it might look like this:
def pending_acceptance(order_fulfillments)
order_fulfillments.select do |o|
o.fulfillment_time_calculator.pending_acceptance?
end
end
def picked_up(order_fulfillments)
order_fulfillments.select do |order_fulfillment|
order_fulfillment.handed_over_late? && order_fulfillment.
fulfillment_time_calculator.pending_handover?
end
end
def calling_code
# order_fulfillments = OrderFulFillments.get_from_somewhere
# Now, filter
collect_fulfillments(pending_start order_fulfillments)
collect_fulfillments(picked_up order_fulfillments)
end
def collect_fullfillments order_fulfillments
order_fulfillments.each {|of| collect_fullfillment(of) }
end
You'll still have 11 (+1) methods, but imho you express more what you are up to - and your colleagues will grok what happens fast, too. Given your example and question I think you should aim for a simple, expressive solution. If you are more "hardcore", use the more functional lambda approach given in the other solutions. Also, note that these approaches could be combined (by passing an iterator).
You could use something like method_missing.
At the bottom of your class, put something like this:
def order_fulfillment_check(method, order_fulfillment)
case method
when "picked_up" then return order_fulfillment.handed_over_late? && order_fulfillment.fulfillment_time_calculator.pending_handover?
...
... [more case statements] ...
...
else return order_fulfillment.fulfillment_time_calculator.send(method + "?")
end
end
def method_missing(method_name, args*, &block)
args[0].each do |order_fulfillment|
next unless order_fulfillment_check(method_name, order_fulfillment);
collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
Depending on your requirements, you could check if the method_name starts with "pending_".
Please note, this code is untested, but it should be somewhere along the line.
Also, as a sidenote, order_fulfillment.fulfillment_time_calculator.some_random_method is actually a violation of the law of demeter. You might want to adress this.

yield to an anonymous block two functions up

there is probably a simple way to do this.
I'm trying to refactor something like the following
def foo(baz)
baz.update_first
if baz.has_condition?
yield baz.val if block_given?
baz.a
else
baz.b
end
end
called like
foo(baz) {|b| b.modify}
to something like
def foo(baz)
baz.update_first
bar(baz) {|i| yield i if block_given? }
end
def bar(baz)
if baz.has_condition?
yield baz.val if block_given?
baz.a
else
baz.b
end
end
Will that work? How?
I think it will, but I'd appreciate a clear explanation of how yielding inside a block works... reading through proc.c and vm.c and a relevant git commit in the ruby source code , I think when bar is called in foo it executes until it yields, and then you walk up the frame stack to the local environment pointer for block defined in foo, which is called, where the yield walks up to the block foo is called with, executes it, and then you are back in bar. Is that correct? Is there a better way to do this?
This feels a little weird to me, like inverting control, and it requires foo to know about baz more then I'd like, but I unfortunately can't simply pass a proc or lambda in this code.
I think maybe the concept of yield will be more clear if you look at an alternative syntax, which is converting the bloc to a proc argument.
For example, the following examples are the same
def my_each(arr)
arr.each { |x| yield x }
end
def my_each(arr, &blk)
arr.each { |x| blk.call(x) }
end
# Both are called the same way
my_each([1,2,3]) { |x| print x }
# => 123
When using yield, the variable is available in the method without declaring it in the parameters list. Prepending an & sign to a parameter converts it to a proc, so in the method it can be run with .call.
Here's an example of providing a block to one method then executing it two scopes in:
def method_a(number, &blk)
method_b do
method_c do
blk.call(number)
end
end
end
def method_b(&blk)
blk.call
end
def method_c(&blk)
blk.call
end
method_a(1) { |num| puts num + 1 }
# => 2
Note that blk is not a magic word - you can name the variable whatever you want.
Here's the same thing with yield:
def method_a(number)
method_b do
method_c do
yield number
end
end
end
def method_b
yield
end
def method_c
yield
end
method_a(1) { |num| puts num + 1 }
# => 2
I think using the &blk syntax is clearer because it assigns a variable to the proc. Just because a proc is used in the method doesn't mean you have to ever run Proc.new. The block is automatically converted to a proc.

blocks in silly blocks rspec testing

I had the following tests given to me as an exercise:
require "silly_blocks"
describe "some silly block functions" do
describe "reverser" do
it "reverses the string returned by the default block" do
result = reverser do
"hello"
end
result.should == "olleh"
end
it "reverses each word in the string returned by the default block" do
result = reverser do
"hello dolly"
end
result.should == "olleh yllod"
end
end
describe "adder" do
it "adds one to the value returned by the default block" do
adder do
5
end.should == 6
end
it "adds 3 to the value returned by the default block" do
adder(3) do
5
end.should == 8
end
end
describe "repeater" do
it "executes the default block" do
block_was_executed = false
repeater do
block_was_executed = true
end
block_was_executed.should == true
end
it "executes the default block 3 times" do
n = 0
repeater(3) do
n += 1
end
n.should == 3
end
it "executes the default block 10 times" do
n = 0
repeater(10) do
n += 1
end
n.should == 10
end
end
end
I was able to solve them with the following code:
def reverser
k = []
x = yield.split(" ")
x.each do |y|
n = y.reverse
k.push(n)
end
m = k.join(" ")
m
end
def adder(num=1, &block)
block.call + num
end
def repeater(num=1, &block)
for i in (1..num) do
block.call
end
end
However I some of these concepts I do not understand all that well. For example:
What exactly does the & symbol in the &block parameter mean?
Similarly what is block.call and where is the actual block object I am assuming its calling?
Could I theoretically use another method on block if I wanted to achieve something else?
Also where can I learn a bit more about blocks
This exercise was a bit above my current knowledge.
It means "this is the block parameter". You are not bound to calling it &block, so there needs to be a way to separate it from the other arguments. The same notation is used to pass arguments to a function as block as opposed to normal arguments (see below)
block.call is exactly the same thing as yield. The difference is that you can use block to access the block itself without calling it immediately. For example, you could store the block for later execution. This is a common pattern known as lazy evaluation.
Yes, you can also pass different things than a do/end block as the &block parameter. See below for some examples.
#UriAgassi gave you an excellent link.
Here are some other things you can pass as block argument. First, just a simple method that takes a block for demonstration:
def reverser(&block)
block.call.reverse
end
You can now pass a standard block
reverser do
"hello"
end
#=> "olleh"
Or, in alternative block syntax, used for inline style
reverser { "hello" }
#=> olleh
You can also pass a lambda or proc, which is similar to a block.
By using the &block notation you can pass a variable as block argument:
my_block = lambda { "hello world!" }
reverser(&my_block)
#=> "!dlrow olleh"
Or, in alternative lambda syntax
my_block = -> { "hello world!" }
reverser(&my_block)
#=> "!dlrow olleh"
You can even take an existing method and pass it as block argument
here you can see the great advantage of blocks: They are evaluated
when block.call is executed, not when the code is loaded. Here this
means that the string will change every time accordingly.
def foobar
"foobar at #{Time.now}"
end
reverser(&method(:foobar))
#=> "0020+ 15:42:90 02-50-4102 ta raboof"
#=> "0020+ 31:52:90 02-50-4102 ta raboof"
You can do cool stuff with this, for example:
[1, 2, 3].each(&method(:puts))
1
2
3
#=> [1, 2, 3]
But remember not to overdo it, Ruby is all about expressive and readable code. Use these techniques when they enhance your code, but use simpler ways if possible.
Finally, here is also an example of lazy evaluation:
class LazyReverser
def initialize(&block)
#block = block
end
def reverse
#block.call.reverse
end
end
reverser = LazyReverser.new do
# some very expensive computation going on here,
# maybe we do not even need it, so lets use the
# lazy reverser!
"hello dolly"
end
# now go and do some other stuff
# it is not until later in the program, that we can decide
# whether or not we even need to call the block at all
if some_condition
reverser.reverse
#=> "yllod olleh"
else
# we did not need the result, so we saved ourselves
# the expensive computation in the block altogether!
end

Ruby/Rails: Prepend, append code to all methods

I wrote a small benchmarking Class for testing my code doing development. At the moment I have to add the Class to the beginning and end of every method. Is it posible to prepend, append on the fly, so that I don't have to clutter my code?
class ApplicationController
before_filter :init_perf
after_filter :write_perf_results_to_log!
def init_perf
#perf ||= Perf.new
end
def write_perf_results_to_log!
#perf.results
end
end
class Products < ApplicationsController
def foo
#perf.log(__methond__.to_s)
caculation = 5 *4
#perf.write!
end
def bar
#perf.log(__methond__.to_s)
caculation = 1 / 5
#perf.write!
end
end
This is the Perf class. It is located in the services folder.
class Perf
def initialize
#results = []
end
def log(note)
#start = Time.now
#note = note
end
def write!
if #results.find {|h| h[:note] == #note } # Update :sec method exists in results
#results.select { |h| h["note"] == #note; h[":sec"] = (Time.now - #start).round(3) }
else # Add new Hash to results
#results << { :note => #note, :sec => (Time.now - #start).round(3) }
end
end
def results
content = "
PERFORMANCE STATISTICS!
"
#results.each do |r|
content += r[:note] + " " + r[:sec].to_s + "
"
end
content += "
"
Rails.logger.info content
end
end
In general computing terms what you want to do is called code instrumentation. There are several ways to accomplish this, however here's one (crude) example using some metaprogramming:
First define a new method that we will use for injecting our instrumentation code:
class ApplicationController
def self.instrument_methods(*methods)
methods.each { |m|
# Rename original method
self.send(:alias_method, "#{m}_orig", m)
# Redefine old method with instrumentation code added
define_method m do
puts "Perf log #{m}"
self.send "#{m}_orig"
puts "Perf write"
end
}
end
end
How to use it:
class Product < ApplicationController
def foo
puts "Foo"
end
def bar
puts "Bar"
end
# This has to be called last, once the original methods are defined
instrument_methods :foo, :bar
end
Then:
p = Product.new
p.foo
p.bar
Will output:
Perf log foo
Foo
Perf write
Perf log bar
Bar
Perf write
Here are some other ways to instrument ruby code and measure performance:
http://ruby-prof.rubyforge.org/
http://www.igvita.com/2009/06/13/profiling-ruby-with-googles-perftools/
There is better solution.
class ApplicationController
def self.inherited(klass)
def klass.method_added(name)
return if #_not_new
#_not_new = true
original = "original #{name}"
alias_method original, name
define_method(name) do |*args, &block|
puts "==> called #{name} with args: #{args.inspect}"
result = send original, *args, &block
puts "<== result is #{result}"
result
end
#_not_new = false
end
end
end
class Product < ApplicationController
def meth(a1, a2)
a1 + a2
end
end
product = Product.new
puts product.meth(2,3)
And the result:
==> called meth with args: [2, 3]
<== result is 5
5
The source & explanation are here: http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming. I recommend to spend not a big money to get this course.
I'm the author of aspector gem. Thanks to dimuch for mentioning it.
I've come up with a solution using aspector. Here are the high level steps:
Create an aspect as a subclass of Aspector::Base
Inside the aspect, define advices (before/after/around are the primary types of advices)
Apply the aspect on target class (or module/object)
The full code can be found in this gist. Please feel free to let me know if you have questions or the solution doesn't do what you intend to.
class PerfAspect < Aspector::Base
around options[:action_methods] do |proxy|
#perf ||= Perf.new
proxy.call
#perf.results
end
around options[:other_methods], :method_arg => true do |method, proxy, *args, &block|
#perf.log(method)
result = proxy.call *args, &block
#perf.write!
result
end
end
action_methods = [:action]
other_methods = Products.instance_methods(false) - action_methods
PerfAspect.apply(Products, :action_methods => action_methods, :other_methods => other_methods)
Guess aspector gem can help. It's not well documented but has useful examples.

Resources