<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: dbroemme</title>
    <description>The latest articles on Forem by dbroemme (@dbroemme).</description>
    <link>https://forem.com/dbroemme</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F858142%2F5b6cb735-3006-4832-922e-e680db624611.png</url>
      <title>Forem: dbroemme</title>
      <link>https://forem.com/dbroemme</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dbroemme"/>
    <language>en</language>
    <item>
      <title>An Introduction to Lambdas in Ruby</title>
      <dc:creator>dbroemme</dc:creator>
      <pubDate>Wed, 28 Jun 2023 11:00:00 +0000</pubDate>
      <link>https://forem.com/appsignal/an-introduction-to-lambdas-in-ruby-4pd3</link>
      <guid>https://forem.com/appsignal/an-introduction-to-lambdas-in-ruby-4pd3</guid>
      <description>&lt;p&gt;Lambdas are a powerful programming construct used in many languages. These functions allow developers to write code that is more concise, efficient, and easier to maintain, making Lambdas an essential part of any developer's toolkit.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how you can use Lambda functions in Ruby to simplify your code and make it more powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Lambda Function?
&lt;/h2&gt;

&lt;p&gt;Lambdas are expressions whose value is a function. For this reason, they are also called anonymous functions. The really nice part about this is that we can reference them using a variable in our code, just like a string, integer, or any other object.&lt;/p&gt;

&lt;p&gt;Everything in Ruby is an object, and Lambdas are no exception. Specifically, a Lambda is an instance of the &lt;a href="https://ruby-doc.org/core-2.7.1/Proc.html"&gt;Ruby Proc class&lt;/a&gt;. They behave the same way as a Proc, except that a Lambda validates the number of parameters. Its use case is targeted towards specific functions, albeit those without a proper name. In fact, the Ruby Proc class has a &lt;code&gt;lambda?&lt;/code&gt; method which returns true if argument handling is rigid (as is the case for a Proc generated by a Lambda).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side-note&lt;/em&gt;: Because of its popularity, it is also important to clarify that AWS Lambda is a different concept to a Lambda in Ruby. AWS Lambda is a cloud-computing service that implements functions without managing the underlying server infrastructure. You can simply write the function in any number of programming languages, including Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Define and Invoke a Lambda Function in Ruby
&lt;/h2&gt;

&lt;p&gt;The canonical form of a Lambda expression is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a_lambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Hello world!"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More commonly in Ruby, you will see the &lt;code&gt;-&amp;gt;&lt;/code&gt; shorthand notation used to define a Lambda expression.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a_lambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Hello world!"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you simply &lt;code&gt;puts a_lambda&lt;/code&gt;, here is what you get.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;a_lambda&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;Proc:0x0000000108f77bb0 10.rb:6 (lambda)&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lambdas can use a block definition notation as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a_lambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Hello world!"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Lambda is invoked as shown below, and the output is as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Hello&lt;/span&gt; &lt;span class="n"&gt;world!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mechanism to invoke a Lambda is the same as it is with a Proc. However, it is different from the way in which traditional functions are called. Note that, if you simply reference &lt;code&gt;a_lambda&lt;/code&gt; in your code, it will not invoke the function. This is equivalent to referencing a variable but doing nothing with it.&lt;/p&gt;

&lt;p&gt;Lambdas can also accept parameters. The following Lambda returns the square of a number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a_lambda_with_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call method can take parameters to match your Lambda signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;three_squared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_lambda_with_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Three squared is &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;three_squared&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This outputs the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Three squared is 9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Ruby offers other syntax options for invoking a Lambda. All of the following statements are equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;val1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_lambda_with_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;val2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_lambda_with_params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;val3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_lambda_with_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;val4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_lambda_with_params&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last few notation alternatives seem quite obtuse, but they work nonetheless.&lt;/p&gt;

&lt;p&gt;Now consider that we want to calculate the square of a set of numbers. We might typically write Ruby code such as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code, what is the expression passed to the map function? It is a block that accepts a single parameter. Thus, it is equivalent to an anonymous function or Lambda. We can rewrite our code as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;square_lambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;square_lambda&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This outputs the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1, 4, 9, 16, 25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the ampersand that precedes the Lambda expression passed to &lt;code&gt;map&lt;/code&gt;. Why is this needed? Well, the ampersand tells Ruby that this is the special block parameter. If we omit this, Ruby thinks that &lt;code&gt;square_lambda&lt;/code&gt; is a 'regular' parameter. Using that approach, we would get the following error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;map&lt;span class="s1"&gt;': wrong number of arguments (given 1, expected 0) (ArgumentError)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the main benefits of a Lambda is the convenience and ease of using the function only once. We could also define a traditional method here, but if this is the only place it would be used, a Lambda function provides a cleaner solution for such a 'disposable' function.&lt;/p&gt;

&lt;p&gt;Another primary benefit of Lambdas is that they provide a portable mechanism to execute code anywhere in your program. This supports some interesting use cases that we'll explore next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda Use Cases in Ruby
&lt;/h2&gt;

&lt;p&gt;There are a number of use cases that work well as Lambda functions in Ruby.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Class Functions
&lt;/h3&gt;

&lt;p&gt;Lambda functions can be passed to other functions for use. This concept is referred to as a first-class function. Consider a use case where you need to execute code at a later point in time, once a certain condition is met.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example: Use a lambda to defer execution of code until later&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_something_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;a_lambda&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@deferred_action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_lambda&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Later, when the condition is met, call the deferred action&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;some_condition_is_met&lt;/span&gt;
  &lt;span class="vi"&gt;@deferred_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Callbacks
&lt;/h3&gt;

&lt;p&gt;Callbacks are another related use case for which Lambdas are useful. Lambdas can be used to implement logic invoked at certain points in the application lifecycle, or a specific object’s lifecycle.&lt;/p&gt;

&lt;p&gt;A good example of this pattern is found in &lt;a href="https://github.com/mongodb/mongoid"&gt;MongoDB's Mongoid framework&lt;/a&gt;. You can set a &lt;code&gt;:default&lt;/code&gt; at the field level, and Lambdas allow you to defer execution until runtime.&lt;/p&gt;

&lt;p&gt;Consider a document with a last modified timestamp. The first implementation runs when the class is loaded. However, execution of the Lambda is deferred until the document is created — the outcome you likely want in your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Static implementation at class load time&lt;/span&gt;
&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:last_modified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

&lt;span class="c1"&gt;# The implementation below defers the lambda execution until document creation time&lt;/span&gt;
&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:last_modified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Visitor Pattern
&lt;/h3&gt;

&lt;p&gt;The visitor pattern in Ruby is another use case that can take advantage of the flexibility of Lambdas. If the visitor logic is not known a priori or otherwise fits the Lambda use cases, visitors can be implemented as Lambda functions.&lt;/p&gt;

&lt;p&gt;Consider the following code snippet for a visitor to a hierarchy of nodes representing mathematical expressions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PrintVisitor&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Visitor&lt;/span&gt;
  &lt;span class="no"&gt;NODE_TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;IntegerNode&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"IntegerNode: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="no"&gt;FloatNode&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"FloatNode: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="no"&gt;ExpressionNode&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"ExpressionNode:"&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;NODE_TYPES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation uses Lambda functions to avoid defining separate methods for each element subclass. Instead, the &lt;code&gt;Visitor&lt;/code&gt; class defines a single visit method that can handle any element subclass by using a Lambda function to call the appropriate method.&lt;/p&gt;

&lt;p&gt;This may be a bit over-engineered, but if you consider cases where the visitor logic is not predefined, Lambdas become much more useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Functional Programming Paradigms
&lt;/h3&gt;

&lt;p&gt;Lambdas in Ruby enable developers to adopt a functional programming paradigm by creating anonymous functions that can be used as arguments in other functions. This approach allows for creating complex functionality by combining smaller, reusable functions. The use of Lambdas in Ruby code results in concise, readable code. They also leverage key functional programming concepts, such as closures and higher-order functions.&lt;/p&gt;

&lt;p&gt;Lambdas can support functional programming features such as currying if you want to take them to that level. Currying permits functions to be partially applied and reused in diverse contexts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metaprogramming
&lt;/h3&gt;

&lt;p&gt;Lambdas are used in metaprogramming to generate code dynamically. Consider the following code that adds a method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example: Use a lambda for dynamic code generation&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;a_lambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;create_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a_lambda&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;hello&lt;/span&gt;   &lt;span class="c1"&gt;# Output: "Hello, world!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lambdas as Closures
&lt;/h3&gt;

&lt;p&gt;A powerful capability of Lambdas is their ability to create a closure. This encapsulates the function as well as the runtime environment, including variables from the surrounding scope. Variables are closed (or bound) to their values in a Lambda. This is why it is called a closed expression or closure. The execution context is stored in an instance of a Ruby Binding object.&lt;/p&gt;

&lt;p&gt;Consider the following example that creates a Lambda which implements an incremental counter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example: Use a lambda as a closure to capture runtime variables&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;   &lt;span class="c1"&gt;# Output: 1&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;   &lt;span class="c1"&gt;# Output: 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Impact of Lambdas on Performance
&lt;/h2&gt;

&lt;p&gt;Performance can be a consideration if you have extremely tight operational requirements. In a tight loop, for example, you will see that Lambdas perform a bit slower than other Ruby code. It is easy to understand why. A Proc object is created to implement a Lambda (as opposed to the runtime engine delegating control to a method).&lt;/p&gt;

&lt;p&gt;In the vast majority of use cases, you will likely not notice much difference in performance (considering that computers can execute thousands of instructions in the time it takes to perform a single database query). Nonetheless, this is something to keep in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this post, we explored some of the fundamentals of Lambdas and Lambda use cases for Ruby.&lt;/p&gt;

&lt;p&gt;Lambdas provide a powerful fundamental programming construct for Ruby developers. They can be convenient one-time use functions, implementation mechanisms for callbacks, and used for several other use cases. Lambdas can also come in handy on projects that adhere to functional programming paradigms.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Audit Logging in Ruby and Rails</title>
      <dc:creator>dbroemme</dc:creator>
      <pubDate>Wed, 19 Apr 2023 13:09:18 +0000</pubDate>
      <link>https://forem.com/appsignal/audit-logging-in-ruby-and-rails-1000</link>
      <guid>https://forem.com/appsignal/audit-logging-in-ruby-and-rails-1000</guid>
      <description>&lt;p&gt;An audit log is a documented record of events and activity within an information technology system. Audit logging is critical for application owners and administrators to track activity within their systems.&lt;/p&gt;

&lt;p&gt;In this post, we'll first dive into what auditing entails and what to consider when audit logging. We'll then explore some options for implementing audit logs, including PaperTrail, Audited, AuditLog, AppSignal, and a custom implementation.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Auditing and Audit Logs: An Introduction
&lt;/h2&gt;

&lt;p&gt;Auditing provides an understanding of system behavior using verifiable, well-defined events. It has a different goal than application logging, which records general-purpose messages often used to diagnose or troubleshoot issues within an application.&lt;/p&gt;

&lt;p&gt;Auditing is typically a foundational requirement for many systems, yet it is often one of the last concerns of development teams. The use of simple, effective patterns upfront can change this trend.&lt;/p&gt;

&lt;p&gt;One motivation for teams to tackle this capability early on is the feedback it provides in terms of metrics for testing and evaluating systems. The observability of audit events can highlight system issues or even missed requirements.&lt;/p&gt;

&lt;p&gt;Audit logs typically have a defined structure that includes the following event information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timestamp&lt;/li&gt;
&lt;li&gt;Event type&lt;/li&gt;
&lt;li&gt;Description&lt;/li&gt;
&lt;li&gt;User or system process that initiated or requested the event&lt;/li&gt;
&lt;li&gt;Impacted entities in the system&lt;/li&gt;
&lt;li&gt;Any additional relevant detail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any system, service, or device can have its own audit log. A collection of audit logs spanning a particular dimension is considered an audit trail of related activity. An audit trail could be specific to a user, application, or system entity. For example, the &lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html"&gt;AWS CloudTrail&lt;/a&gt; service records all activity within an AWS account.&lt;/p&gt;

&lt;p&gt;Audit logs support, among others, the following key functions and use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compliance and regulatory requirements&lt;/li&gt;
&lt;li&gt;Security reviews and investigations of potential security breaches&lt;/li&gt;
&lt;li&gt;Determining what users make particular changes in a system and when&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Design Considerations for Audit Capabilities
&lt;/h2&gt;

&lt;p&gt;Audit logging solutions typically involve a centralized log management system. Each application instance can directly send audit events to the centralized repository, or the solution may include agents running on compute nodes that facilitate this communication.&lt;/p&gt;

&lt;p&gt;Distributed architectures can make auditing challenging because there are many sources of audit events throughout the infrastructure. Centralized solutions aim to funnel all of the disparate audit events through a central pipeline for processing.&lt;/p&gt;

&lt;p&gt;Audit management systems provide storage and search capabilities for the collection of audit events. There are several persistence options, although many Ruby and Rails-based solutions use database persistence to store audit events as a natural extension of ActiveRecord capabilities.&lt;/p&gt;

&lt;p&gt;As an alternative, cloud data streaming services can also be used. For example, &lt;a href="https://aws.amazon.com/kinesis/"&gt;AWS Kinesis&lt;/a&gt; is designed to handle extremely large volumes of streaming data and is well suited for the audit use case. Another option is to store audit data in &lt;a href="https://aws.amazon.com/s3/"&gt;AWS S3 buckets&lt;/a&gt; and then query it using &lt;a href="https://aws.amazon.com/athena"&gt;AWS Athena&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Constitutes an Audit Event in Ruby and Rails?
&lt;/h2&gt;

&lt;p&gt;Countless events occur within a system, so one of the most important decisions is determining what exactly to audit. Audit events can be based on functional requirements (e.g., a user submits an order) or non-functional requirements (e.g., a security-relevant event occurs, such as a failed login attempt). Both types are important and serve different purposes. Certainly, security auditing is of foremost concern in today’s operating environment.&lt;/p&gt;

&lt;p&gt;The level of detail is another key consideration. A single business transaction may involve updates to multiple database tables. While a change in an individual database entity could be the subject of an audit event, by itself this may not contain enough information about the overarching event taking place. It is often beneficial to audit the business function in addition to, or even in place of, individual database rows. Business events typically occur within the service or controller level code.&lt;/p&gt;

&lt;p&gt;Some use cases require all of the detail you have. For example, requirements may dictate that all user HTTP requests must be audited. You may need to know which users queried for particular information in comprehensive security audits. As is often the case in software engineering, let your requirements be your guide in making these design decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruby Audit Log Implementation Options
&lt;/h2&gt;

&lt;p&gt;Most Ruby gems in this space are focused on the Rails use case, as it has ORM capabilities to identify changes to ActiveRecord instances and store them as audit events. This enables basic audit capabilities without much development effort. Keep in mind, however, the level of detail of your audit events. You may need to add code to specifically audit business events in your service or controller classes.&lt;/p&gt;

&lt;p&gt;AppSignal also has a new &lt;a href="https://www.appsignal.com/tour/log-management"&gt;logging feature&lt;/a&gt; as part of its integration for Ruby. Applications can log messages and structured event data from their applications which are stored on managed AppSignal servers. Once you've signed up, you can use AppSignal dashboards to search, filter, and view your logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  PaperTrail
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/paper-trail-gem/paper_trail"&gt;PaperTrail&lt;/a&gt; gem is a popular choice for audit logging in Ruby. It is focused on tracking changes to ActiveRecord model classes using a versions table. It allows you to see how a model looks at any point in its lifecycle. Several configuration options determine what type of model changes result in creating an audit event, or version. For example, you can choose to audit all changes or only create audit events where designated attributes are modified.&lt;/p&gt;

&lt;p&gt;If you have an application that requires maintaining a history of changes over time, you will find this gem particularly useful. There is a good deal of overlap between auditing and versioning capabilities, and PaperTrail provides a nice solution for both. A &lt;code&gt;versions&lt;/code&gt; method is added to each model class.&lt;/p&gt;

&lt;p&gt;You can easily see the changes between versions as well, as shown in &lt;a href="https://github.com/paper-trail-gem/paper_trail#3c-diffing-versions"&gt;this code snippet from the PaperTrail documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Bob'&lt;/span&gt;
&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changeset&lt;/span&gt; &lt;span class="c1"&gt;# reads object_changes column&lt;/span&gt;
&lt;span class="c1"&gt;# {&lt;/span&gt;
&lt;span class="c1"&gt;#   "name"=&amp;gt;[nil, "Bob"],&lt;/span&gt;
&lt;span class="c1"&gt;#   "created_at"=&amp;gt;[nil, 2015-08-10 04:10:40 UTC],&lt;/span&gt;
&lt;span class="c1"&gt;#   "updated_at"=&amp;gt;[nil, 2015-08-10 04:10:40 UTC],&lt;/span&gt;
&lt;span class="c1"&gt;#   "id"=&amp;gt;[nil, 1]&lt;/span&gt;
&lt;span class="c1"&gt;# }&lt;/span&gt;
&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Robert'&lt;/span&gt;
&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changeset&lt;/span&gt;
&lt;span class="c1"&gt;# {&lt;/span&gt;
&lt;span class="c1"&gt;#   "name"=&amp;gt;["Bob", "Robert"],&lt;/span&gt;
&lt;span class="c1"&gt;#   "updated_at"=&amp;gt;[2015-08-10 04:13:19 UTC, 2015-08-10 04:13:19 UTC]&lt;/span&gt;
&lt;span class="c1"&gt;# }&lt;/span&gt;
&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;
&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changeset&lt;/span&gt;
&lt;span class="c1"&gt;# {}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As long as your controller has a &lt;code&gt;current_user&lt;/code&gt; method, it will track who is responsible for changes using the following callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_paper_trail_whodunnit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One drawback is the lack of a well-defined pattern for custom audit events. A workaround is to create a database entity that captures relevant audit information, although that somewhat defeats the purpose of this gem’s paradigm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audited
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/collectiveidea/audited"&gt;Audited&lt;/a&gt; gem is another implementation similar in scope to PaperTrail. It now only focuses on ActiveRecord support. An opt-in approach is used to denote which model classes should be audited, as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;audited&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Model classes get a &lt;code&gt;revisions&lt;/code&gt; method similar to PaperTrail’s &lt;code&gt;versions&lt;/code&gt;. Audited models also get an &lt;code&gt;audits&lt;/code&gt; method that provides change-tracking information.&lt;/p&gt;

&lt;p&gt;However, this choice suffers from the same drawback as PaperTrail: a lack of support for custom audit events.&lt;/p&gt;

&lt;h3&gt;
  
  
  AuditLog
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/rails-engine/audit-log"&gt;AuditLog&lt;/a&gt; takes a different approach. Rather than centering the design around model classes, it uses explicit calls to create audit events, as shown in the code below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;AuditLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;audit!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:update_password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote_ip&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="no"&gt;AuditLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;audit!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sign_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote_ip&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="no"&gt;AuditLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;audit!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:create_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to audit individual model entities, you can use code similar to the following to accomplish this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TicketsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="n"&gt;audit!&lt;/span&gt; &lt;span class="ss"&gt;:list_tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="n"&gt;audit!&lt;/span&gt; &lt;span class="ss"&gt;:create_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="n"&gt;ticket_params&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="n"&gt;audit!&lt;/span&gt; &lt;span class="ss"&gt;:update_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="n"&gt;ticket_params&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PaperTrail and Audited can do this automatically for you. However, in this code sample, you can also see the use of action-based auditing in the index method. This equates to an audit event capturing a view tickets operation.&lt;/p&gt;

&lt;p&gt;AuditLog also provides a simple web interface to view audit information. While your operational requirements may dictate a more comprehensive reporting capability, this is a nice feature to have right out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audit Logging with AppSignal
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.appsignal.com/tour/log-management"&gt;AppSignal provides a managed logging mechanism&lt;/a&gt; that offloads the storage of log data and provides a comprehensive set of search and filter capabilities. Each application has a default log source, or you can create additional sources as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xD2diqII--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-04/new-log-source.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xD2diqII--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-04/new-log-source.png" alt="Image of web form to create a new logging source" width="729" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following code shows an example of creating an audit log event from a sample blog application. The logger constructor takes a group name as a parameter that can be used to identify event sources. This code example comes from an articles controller class where new articles are created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"articles"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Created new article &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;article_id: &lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AppSignal dashboards can be used to view log data, both at the aggregate level as well as detailed audit log information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XN_D3jlY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-04/view-audit-logs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XN_D3jlY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-04/view-audit-logs.png" alt="Image of AppSignal logging dashboard" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can drill down into specific log events and view the structured data included with the event, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--40xBW8SY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-04/audit-log-details.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--40xBW8SY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-04/audit-log-details.png" alt="Image of AppSignal detailed log event" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Audit Logging Implementation
&lt;/h3&gt;

&lt;p&gt;You can also roll your own implementation if you have specific requirements that don’t fit one of these solutions. In most cases, you will need to implement additional reporting capabilities using the stored audit data.&lt;/p&gt;

&lt;p&gt;For this, you may choose to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use another Ruby gem.&lt;/li&gt;
&lt;li&gt;Push the data to a data warehouse or cloud service for analysis and long-term storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Choosing an Audit Logging Implementation
&lt;/h2&gt;

&lt;p&gt;Here are a few key considerations to help you choose between audit solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What types of events do you need to audit?&lt;/strong&gt; Are they primarily database-driven events? If so, a solution like PaperTrail is a great choice. If you have higher-level business events, then you need support for custom audit events similar to the pattern implemented by AuditLog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do gems with higher activity levels give you more confidence?&lt;/strong&gt; If so, PaperTrail and Audited are two great choices. AuditLog provides more flexibility, but it has less of an installed user base.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What volume of audit events do you need to capture?&lt;/strong&gt; Will the storage of audit events in the same application database add significant overhead to your application? If so, consider using AppSignal for minimal impact to your application's performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remember that you also need query and reporting capabilities.&lt;/strong&gt; The out-of-the-box capability here varies widely across the different options. AppSignal provides a comprehensive search and filter capability over log data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Audit logging is a fundamental capability for almost all business applications. We've seen that it is beneficial to establish a design pattern early on for the observability of a system as development progresses.&lt;/p&gt;

&lt;p&gt;There are some great implementations available for Ruby and Rails developers: PaperTrail, Audited, AuditLog, and AppSignal. Evaluate your requirements using the criteria discussed above to determine the best choice for your needs.&lt;/p&gt;

&lt;p&gt;Happy audit logging!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Monitor Ruby Application Performance with Magic Dashboards</title>
      <dc:creator>dbroemme</dc:creator>
      <pubDate>Tue, 13 Sep 2022 13:04:50 +0000</pubDate>
      <link>https://forem.com/appsignal/monitor-ruby-application-performance-with-magic-dashboards-47g1</link>
      <guid>https://forem.com/appsignal/monitor-ruby-application-performance-with-magic-dashboards-47g1</guid>
      <description>&lt;p&gt;Application teams must understand what their customer experience is like. This is true not only from a general perspective (in terms of usability and responsiveness) but also on a day-to-day, minute-by-minute basis.&lt;/p&gt;

&lt;p&gt;In particular, when you work with distributed systems, errors are inevitable. Site traffic fluctuates throughout the day, and any one of a system’s dependencies could also encounter an issue at any time.&lt;/p&gt;

&lt;p&gt;In this article, we'll use magic dashboards to help monitor and resolve performance issues within a Ruby on Rails application.&lt;/p&gt;

&lt;p&gt;But before we dive into magic dashboards, let's see what we should look out for when designing our apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to Consider When Building A Ruby App
&lt;/h2&gt;

&lt;p&gt;As an application owner or team member, you'll want to know if there are any problems with your application before the customer does. This enables you to take corrective action immediately and hopefully avoid any disruption to your users.&lt;/p&gt;

&lt;p&gt;There are a few key application questions you need to be able to answer at any point in time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the page response time acceptable? According to Google, these days anything &lt;a href="https://www.youtube.com/watch?v=OpMfx_Zie2g"&gt;slower than two seconds&lt;/a&gt; will cause customers to leave your site and go elsewhere.&lt;/li&gt;
&lt;li&gt;Are your users experiencing any errors? If so, what types of errors? What is the error rate?&lt;/li&gt;
&lt;li&gt;Are any ongoing operational issues affecting your application? This could be with the network, storage, or security services. As we all know, cloud providers have outages that impact our application’s health as well.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Observability is key, yet it is often the last thing engineers think about. Besides, there is already time built into the schedule for operational readiness. It's called the weekend before the product launch!&lt;/p&gt;

&lt;p&gt;Joking aside, you have to build your app first before something exists to be observed. This lends itself to delaying performance testing until close to the end of the development lifecycle. You don’t want to spend too much time performance testing when the product isn’t code-complete, because then you’ll have to do it all over again later.&lt;/p&gt;

&lt;p&gt;The same is true for security testing. If you conduct it too early, a vulnerability could still be introduced after testing, but before a product goes into production.&lt;/p&gt;

&lt;p&gt;So, we know monitoring is critical, but all of these concerns are fair points. Monitoring doesn’t get the attention it deserves until late in the process. That’s why AppSignal created &lt;a href="https://blog.appsignal.com/2019/03/27/magic-dashboards.html"&gt;magic dashboards&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Magic Dashboards in AppSignal
&lt;/h2&gt;

&lt;p&gt;AppSignal understands the importance of metrics, dashboards, and your app's performance — but also that engineers have minimal time to work on these before launch.&lt;/p&gt;

&lt;p&gt;Our magic dashboards are called "magic" because the metrics collection and associated dashboards are created automatically for you, simply when you connect your app and integrated components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Magic Dashboards: An Example Ruby on Rails App
&lt;/h2&gt;

&lt;p&gt;Let's find out how we can monitor and resolve performance issues within a Ruby on Rails app using magic dashboards.&lt;/p&gt;

&lt;p&gt;Our application uses a simple machine learning (ML) model for cryptocurrency price prediction. An asynchronous job updates the model daily with the latest price data and improves accuracy over time. This Rails app has both web pages and a REST API, as shown in the architecture diagram below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oEuXuxR3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/magic-dashboard-app-architecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oEuXuxR3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/magic-dashboard-app-architecture.png" alt="Architecture diagram for the sample application" width="880" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After we deploy our Rails app, magic dashboards are automatically created for the Rails &lt;a href="https://docs.appsignal.com/ruby/integrations/puma.html#minutely-probe"&gt;Puma&lt;/a&gt; web server and the &lt;a href="https://docs.appsignal.com/ruby/integrations/sidekiq.html#minutely-probe"&gt;Sidekiq&lt;/a&gt; asynchronous jobs. Other supported integrations for magic dashboards include &lt;a href="https://docs.appsignal.com/ruby/integrations/mongodb.html"&gt;MongoDB&lt;/a&gt; and the &lt;a href="https://docs.appsignal.com/elixir/integrations/erlang.html#minutely-probe"&gt;Erlang VM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can use the Puma magic dashboard to evaluate performance based on threads, pool capacity, and puma workers. The Sidekiq magic dashboard monitors queue length, queue latency, job duration, job status, and memory usage.&lt;/p&gt;

&lt;p&gt;Magic dashboards are detected and created based on the use of event-based metrics such as a Sidekiq job run, as well as &lt;a href="https://docs.appsignal.com/ruby/instrumentation/minutely-probes.html"&gt;minutely probes&lt;/a&gt;. The minutely probe feature allows you to register a Ruby block or class to send custom metrics to AppSignal. Magic dashboards provide out-of-the-box integration for supported components, but you can leverage this mechanism to send your custom metrics to AppSignal.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Example of Custom Minutely Probes
&lt;/h2&gt;

&lt;p&gt;Sidekiq integration is already built in, but imagine you want to monitor a proprietary background job mechanism. You can use the following class example. The probe class obtains a connection that is then used on each call to get the desired metrics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/appsignal.rb or a file that's loaded on boot&lt;/span&gt;

&lt;span class="c1"&gt;# Creating a probe using a Ruby class&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BackgroundJobLibraryProbe&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="c1"&gt;# This is only called when the minutely probe gets initialized&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"background_job_library"&lt;/span&gt;
    &lt;span class="vi"&gt;@connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BackgroundJobLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_queue_stats&lt;/span&gt;
    &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_gauge&lt;/span&gt; &lt;span class="s2"&gt;"background_job_library_queue_length"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queue_length&lt;/span&gt;
    &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_gauge&lt;/span&gt; &lt;span class="s2"&gt;"background_job_library_processed_jobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processed_jobs&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Registering a Class probe&lt;/span&gt;
&lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Minutely&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;probes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;:background_job_library_probe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BackgroundJobLibraryProbe&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call to &lt;code&gt;Appsignal::Minutely.probes.register&lt;/code&gt; takes two parameters — a name for the probe and its implementation, which can be either a lambda or a class that implements the call method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration and System Requirements for AppSignal
&lt;/h2&gt;

&lt;p&gt;Simply use AppSignal with your Rails application to get magic dashboards. If you haven’t already installed AppSignal, include the appsignal gem in your Gemfile and run a bundle install.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'appsignal'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the &lt;code&gt;appsignal install&lt;/code&gt; command to configure your environment. This configuration can be stored in a config file or environment variables, and connects your application to your AppSignal account and dashboards.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;appsignal &lt;span class="nb"&gt;install &lt;/span&gt;appsignal-license-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you install and run your server, you will see a few emails similar to the following.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sWYjJmEw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/email-notification.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sWYjJmEw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/email-notification.png" alt="Informational email that a Sidekiq magic dashboard was created" width="880" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The system requirements for this example are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AppSignal gem 2.9.0 or higher, which includes support for the minutely probe.&lt;/li&gt;
&lt;li&gt;Puma integration - requires version 3.11.4 or higher.&lt;/li&gt;
&lt;li&gt;Sidekiq integration - requires the &lt;a href="https://rubygems.org/gems/redis/"&gt;Redis gem&lt;/a&gt; 3.3.5 or higher. For this integration, the AppSignal gem 2.9.5 or higher is recommended.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Testing our Price Prediction API
&lt;/h2&gt;

&lt;p&gt;The above example application uses a simple neural network implemented using the &lt;a href="https://github.com/tangledpath/ruby-fann"&gt;Ruby FANN gem&lt;/a&gt; to predict the next day's Bitcoin price. Percentage price changes from the last ten days are used as inputs to the model.&lt;/p&gt;

&lt;p&gt;The REST API doesn't take any parameters, as it currently only predicts the price for tomorrow. The output is a simple JSON document, as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;http://localhost:3000/crypto/predict_api

&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"day"&lt;/span&gt;:&lt;span class="s2"&gt;"2022-07-22"&lt;/span&gt;,
  &lt;span class="s2"&gt;"price"&lt;/span&gt;:23080.95619
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Sidekiq job gets the market price at the beginning of each day and updates the ML model. This job is scheduled to run every five minutes, enabling it to catch up to any outages or errors quickly.&lt;/p&gt;

&lt;p&gt;You can find all of the &lt;a href="https://github.com/dbroemme/btc-price-prediction"&gt;code from this article on GitHub&lt;/a&gt;. &lt;em&gt;Please note that nothing in this article or the software constitutes investment advice. Consult a financial advisor before making any investment decisions regarding cryptocurrency.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We'll use JMeter to simulate load on the REST API for performance testing. This allows us to generate traffic and evaluate performance using our magic dashboards. Our first test uses 5 concurrent clients, each making 100 requests.&lt;/p&gt;

&lt;p&gt;The statistics show a fairly high range in response time, with an average of 342ms, but the P99 is 852ms. It seems that we can do better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MbtG3X8q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/jmeter-report-five-threads.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MbtG3X8q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/jmeter-report-five-threads.png" alt="Summary report of test with five Puma threads" width="880" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving Rails App Performance with the Puma Magic Dashboard
&lt;/h2&gt;

&lt;p&gt;Our Puma magic dashboard includes a graph of the thread pool capacity, and we can see that it touches zero during our test run. This would explain why some requests take longer than others, so we'll increase the number of puma threads to 10.&lt;/p&gt;

&lt;p&gt;Keep in mind, we did nothing to create the dashboard or this graph. AppSignal automatically created this for us.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7z6S0JRo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/puma-dashboard-pool-capacity-10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7z6S0JRo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/puma-dashboard-pool-capacity-10.png" alt="Graph of Puma pool availability during multiple test runs" width="880" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After running the test with the increased puma thread count, the response times are much more consistent, and the dashboard confirms that pool capacity stays within acceptable levels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m83uMN6d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/jmeter-report-ten-threads.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m83uMN6d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/jmeter-report-ten-threads.png" alt="Summary report of test with ten Puma threads" width="880" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it is time to turn up the dial. The number of JMeter concurrent clients is increased to 50, with a ramp-up time of 5 seconds. This test shows a poor response time and a number of API errors. The Puma magic dashboard again shows the available puma threads reaching zero.&lt;/p&gt;

&lt;p&gt;Looking at the API code, we find that the model is being loaded from the database each time. This is not very efficient, so we change the Sidekiq job to not only grab the new daily price, but also run the ML model and save the prediction in the database.&lt;/p&gt;

&lt;p&gt;We deploy this change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better API Performance with Sidekiq's Magic Dashboard
&lt;/h2&gt;

&lt;p&gt;Now let's check our Sidekiq magic dashboard. Unfortunately, there is a bug in the code, but at least we can identify and fix it quickly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NV_Jiabx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/sidekiq-error-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NV_Jiabx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/sidekiq-error-graph.png" alt="Graph of Sidekiq job status over time" width="880" height="710"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the Sidekiq &lt;code&gt;PriceUpdateJob&lt;/code&gt; now working, the API is modified so that it only needs to retrieve the predicted price from the database. This improves the API performance, but we still see some API errors and lengthy response times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the Puma Magic Dashboard
&lt;/h2&gt;

&lt;p&gt;A glance at the dashboard highlights that we have not yet configured Puma to use additional workers. A Puma worker is an OS-level process that can run several threads. The total thread count is calculated as the number of workers multiplied by the maximum threads. First, we use 2 workers, but the available threads are still exhausted. So we'll increase the amount to 4 workers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DpgE9nW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/puma-four-workers.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DpgE9nW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/puma-four-workers.png" alt="Graph of Puma workers over time" width="880" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our API performance is now very good. The average response time is 419ms, and there are no API errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U3QWE1Kh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/puma-capacity-four-workers.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U3QWE1Kh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/puma-capacity-four-workers.png" alt="Graph of Puma thread capacity over time" width="880" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The magic dashboard confirms that there is still available thread capacity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HZZG6Mm2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/jmeter-report-four-workers.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HZZG6Mm2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-08/jmeter-report-four-workers.png" alt="JMeter test report with four workers" width="880" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AppSignal's magic dashboards give us instant insights into the capacity of the Puma web server. The Sidekiq and Active Worker dashboards provide similar insights into our application's asynchronous jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We've Learned from Magic Dashboards
&lt;/h3&gt;

&lt;p&gt;During our Rails application performance testing, the Puma magic dashboard helped us easily identify that the number of threads was insufficient for our desired throughput level. It shows the thread pool capacity, number of workers over time, and the total number of threads.&lt;/p&gt;

&lt;p&gt;While the Sidekiq job did not have any performance issues, the magic dashboard helped us quickly identify that there was an error after deployment, which we were able to fix and redeploy rapidly. All of these monitoring capabilities were set up for us automatically by AppSignal.&lt;/p&gt;

&lt;h2&gt;
  
  
  AppSignal's Dashboard Features for Ruby and Rails Apps
&lt;/h2&gt;

&lt;p&gt;Dashboards in AppSignal have namespaces, such as "web" application or "background" jobs, so you can create numerous monitoring views of your application. The built-in summary dashboard shows an overview of your application's health, including throughput, response time, and the latest errors.&lt;/p&gt;

&lt;p&gt;Note that, as with any dashboard, you can edit what graphs and metrics are shown, as well as change the layout and the configuration of selected graphs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.appsignal.com/anomaly-detection#main"&gt;Anomaly detection&lt;/a&gt; is a powerful feature. It allows you to define thresholds that send notifications when a metric value goes over or below a given value, such as free memory or the error rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up: Monitor Your Ruby App Today with AppSignal
&lt;/h2&gt;

&lt;p&gt;Observability and performance are critical to the success of our applications. However, we often wait until the last minute to deal with these topics.&lt;/p&gt;

&lt;p&gt;AppSignal's magic dashboards provide extensive out-of-the-box metrics, dashboards, and insights that are set up automatically. This allows you to rapidly tune your application’s performance and reach a state of operational readiness.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.appsignal.com/ruby"&gt;Read more about AppSignal for Ruby&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until next time, happy coding!&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Using Scientist to Refactor Critical Ruby on Rails Code</title>
      <dc:creator>dbroemme</dc:creator>
      <pubDate>Wed, 25 May 2022 11:11:04 +0000</pubDate>
      <link>https://forem.com/appsignal/using-scientist-to-refactor-critical-ruby-on-rails-code-30g8</link>
      <guid>https://forem.com/appsignal/using-scientist-to-refactor-critical-ruby-on-rails-code-30g8</guid>
      <description>&lt;p&gt;Ask any software engineer to review key portions of production code, and inevitably, they will point out three things that need to be refactored. So why does so much bad, brittle, or misunderstood code remain running in production?&lt;/p&gt;

&lt;p&gt;The answer is simple: engineers are afraid to touch it. Refactoring tasks get identified and added to the backlog, but rarely make it into the current sprint.&lt;/p&gt;

&lt;p&gt;There are numerous reasons for this. The code may have been written by an engineer who left the team years ago, and no one completely understands it. In other cases, the capability is critical to the business. No one wants to be responsible for a potential outage or loss of revenue.&lt;/p&gt;

&lt;p&gt;In this post, we'll examine how you can use Scientist to migrate, refactor, and change critical Ruby production code with confidence.&lt;/p&gt;

&lt;p&gt;But first, you might ask — can't we use tests to dig up code issues?&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is What Rails Testing Is For, Right?
&lt;/h2&gt;

&lt;p&gt;Yes, and no. It is often difficult to gain complete confidence in code changes before deployment. The unit and system tests pass. It's good to go, right?&lt;/p&gt;

&lt;p&gt;The reality is that there is no substitute for the real world, i.e. production. What if the data quality is bad or tests are missing? How can you know if the new software will perform well enough to handle production throughput?&lt;/p&gt;

&lt;p&gt;Teams with public services sometimes find that they need to deal with “bugwards compatibility” issues. When a bug has existed in production for a while, clients may code in a way that depends on consistent incorrect behavior. Customers often use software in unexpected ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observe Production Changes in Ruby and Rails with Scientist
&lt;/h2&gt;

&lt;p&gt;If production is the best place to gain confidence in a change, then consider observing how code behaves there. This may sound scary at first, as the idea of “testing in production” contradicts classic software engineering practices.&lt;/p&gt;

&lt;p&gt;However, the good news is that it’s easy and safe to do so in Ruby and Rails using the &lt;a href="https://github.com/github/scientist"&gt;Scientist gem&lt;/a&gt;. Scientist's name is based on the scientific method of conducting experiments to verify a given hypothesis. In this case, our hypothesis is that the new code does the job.&lt;/p&gt;

&lt;p&gt;The reason we can safely take this approach stems from the fact that experiments still use the result of the existing code. New code is only evaluated for observation and comparison purposes, both for accuracy and performance. We mitigate the test coverage concerns discussed earlier by evaluating performance using real-world data and parameters. Experiments typically evaluate a chosen sample rate of requests to minimize the impact on production. However, you can evaluate every request if desired.&lt;/p&gt;

&lt;p&gt;Let's now take a quick look at how Scientist works in a branch by abstraction way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Branch by Abstraction Pattern in Ruby's Scientist
&lt;/h2&gt;

&lt;p&gt;Scientist's approach begins with the &lt;a href="https://martinfowler.com/bliki/BranchByAbstraction.html"&gt;Branch by Abstraction&lt;/a&gt; pattern described by Martin Fowler as making “a large-scale change to a software system in a gradual way.”&lt;/p&gt;

&lt;p&gt;We introduce an abstraction layer to isolate the code being updated. This layer decides which implementation to use so that the experiment is transparent to the rest of the system. The technique is related to using a feature flag that determines the code path.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.blog/2016-02-03-scientist/"&gt;Scientist gem, which originated from Github&lt;/a&gt;, implements this pattern using an experiment. The existing code is referred to as the control, and the new implementation is the candidate. Both code paths are run in randomized order, but only the control result is returned to the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Scientist to Refactor a Ruby Service
&lt;/h2&gt;

&lt;p&gt;Consider a &lt;a href="https://github.com/dbroemme/scientist-labtech-example/blob/master/app/helpers/prime_factor_helper.rb#L20"&gt;Ruby service&lt;/a&gt; that returns the largest prime factor for a given number. Assume that we've identified optimizations to prune the required set of candidates, speeding up the service.&lt;/p&gt;

&lt;p&gt;However, service owners want to be sure no bugs were introduced. They also want to observe any performance improvements. Introduce the &lt;a href="https://github.com/dbroemme/scientist-labtech-example/blob/master/app/helpers/prime_factor_helper.rb#L3"&gt;following code&lt;/a&gt;, modifying clients to call this method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'scientist'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;largest_prime_factor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;science&lt;/span&gt; &lt;span class="s2"&gt;"prime-factors"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;experiment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;experiment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;find_largest_prime_factor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;# old way&lt;/span&gt;
    &lt;span class="n"&gt;experiment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;improved_largest_prime_factor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# new way&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;  &lt;span class="c1"&gt;# returns the control value&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, only the &lt;code&gt;use&lt;/code&gt; (control) expression is invoked. To make the experiment worthwhile, define a custom &lt;code&gt;Experiment&lt;/code&gt; class to enable it (100% of the time below) and publish the results (in this case, just logging). Scientist generates fantastic data but it doesn’t do anything with it by default. That part is left up to you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'scientist/experiment'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'pp'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyExperiment&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Scientist&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Experiment&lt;/span&gt;

  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enabled?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;raised&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s2"&gt;"Operation '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' failed with error '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="c1"&gt;# will re-raise&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results of the experiment will be logged, and we can make improvements over time based on the feedback. Once the new code meets the requirements and confidence is high, accomplish cutover to the new implementation by simply replacing the science code with a delegation to the new implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  LabTech Simplifies Scientist Experiments in Ruby on Rails
&lt;/h2&gt;

&lt;p&gt;You can use the &lt;a href="https://github.com/RealGeeks/lab_tech"&gt;LabTech gem&lt;/a&gt; in your Rails application to easily configure Scientist and handle the results.&lt;/p&gt;

&lt;p&gt;Applications that use AppSignal can use the &lt;code&gt;Appsignal.instrument&lt;/code&gt; &lt;a href="https://docs.appsignal.com/ruby/instrumentation/instrumentation.html"&gt;custom instrumentation helper&lt;/a&gt; to track how long Scientist events take to complete. Wrap it around the different experiment code blocks to see events appear in the performance timeline.&lt;/p&gt;

&lt;p&gt;Now, going back to LabTech — the web page below simply accepts a number to factor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N0IoK3vN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-05/prime-factor-screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N0IoK3vN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-05/prime-factor-screenshot.png" alt="Image of web form to enter a number and see prime factor" width="447" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Getting started is easy provided you have access to the console. First, add the LabTech gem to your Gemfile and run a &lt;code&gt;bundle install&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'lab_tech'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tables store results and experiment configuration, so run a database migration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails lab_tech:install:migrations db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The abstraction layer is the same, except the LabTech module is used. The full code is available on GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;largest_prime_factor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;LabTech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;science&lt;/span&gt; &lt;span class="s2"&gt;"prime-factors"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;experiment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the experiment is disabled, so use the console to enable it in all cases or for a percentage of the time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails console
LabTech.enable &lt;span class="s2"&gt;"prime-factors"&lt;/span&gt;
LabTech.enable &lt;span class="s2"&gt;"prime-factors"&lt;/span&gt;, percent: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now run tests and the experiment will be evaluated. For a textual view of results, use either of the following commands from the Rails console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;LabTech.summarize_results &lt;span class="s2"&gt;"prime-factors"&lt;/span&gt;
LabTech.summarize_errors &lt;span class="s2"&gt;"prime-factors"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few successful runs and one manufactured error, here is an example of what the results summary looks like. There is an overview of successes and failures, as well as an ASCII chart showing performance differences.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--------------------------------------------------------------------------------&lt;/span&gt;
Experiment: prime-factors
&lt;span class="nt"&gt;--------------------------------------------------------------------------------&lt;/span&gt;
Earliest results: 2022-04-27T02:42:45Z
Latest result:    2022-05-01T17:27:39Z &lt;span class="o"&gt;(&lt;/span&gt;5 days&lt;span class="o"&gt;)&lt;/span&gt;

3 of 4 &lt;span class="o"&gt;(&lt;/span&gt;75.00%&lt;span class="o"&gt;)&lt;/span&gt; correct
1 of 4 &lt;span class="o"&gt;(&lt;/span&gt;25.00%&lt;span class="o"&gt;)&lt;/span&gt; mismatched

Median &lt;span class="nb"&gt;time &lt;/span&gt;delta: +0.000s  &lt;span class="o"&gt;(&lt;/span&gt;90% of observations between +0.000s and +0.000s&lt;span class="o"&gt;)&lt;/span&gt;

Speedups &lt;span class="o"&gt;(&lt;/span&gt;by percentiles&lt;span class="o"&gt;)&lt;/span&gt;:
      0%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
      5%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     10%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     15%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     20%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     25%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     30%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     35%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     40%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     45%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     50%  &lt;span class="o"&gt;[&lt;/span&gt; · · · · · · · · · · · · · · · · · █ · · · · · · · &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     55%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     60%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     65%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·         █               &lt;span class="o"&gt;]&lt;/span&gt;    +2.4x faster
     70%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·                        █]    +6.9x faster
     75%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·                        █]    +6.9x faster
     80%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·                        █]    +6.9x faster
     85%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·                        █]    +6.9x faster
     90%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·                        █]    +6.9x faster
     95%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·                        █]    +6.9x faster
    100%  &lt;span class="o"&gt;[&lt;/span&gt;                         ·                        █]    +6.9x faster
&lt;span class="nt"&gt;--------------------------------------------------------------------------------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/ankane/blazer"&gt;Blazer gem&lt;/a&gt; provides a nice way to analyze the results easily. It is simple to install and allows SQL queries to run against tables. The query here shows that the candidate implementation is significantly faster than the original.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j-GZKBqF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-05/blazer-query.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j-GZKBqF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-05/blazer-query.png" alt="Image of experiment observations using Blazer" width="880" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the example prime factoring service, the speedup in the improved implementation comes from a heuristic that eliminates some possible factors to consider. As we consider higher numbers and find a prime factor, we can stop searching after we get to our target number divided by that factor. The new code path only adds &lt;a href="https://github.com/dbroemme/scientist-labtech-example/blob/master/app/helpers/prime_factor_helper.rb#L38"&gt;one statement&lt;/a&gt; to accomplish this.&lt;/p&gt;

&lt;p&gt;We can also see the reduction in execution time using a Blazer query against the LabTech tables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8799kwGZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-05/speedup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8799kwGZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-05/speedup.png" alt="Image of bar graph showing speedup of new code" width="880" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases and Limitations of Scientist
&lt;/h2&gt;

&lt;p&gt;Optimal use cases for Scientist include searches, calculations, and code that has no side effects. Code that includes transactional updates or external integrations such as email does not fit cleanly into the model because the capability is run twice (both the old and new implementations).&lt;/p&gt;

&lt;p&gt;This is not a trivial limitation, as it does eliminate several use cases. However, there are some workarounds if the experiment is critical to your success. Consider whether the side effects are relevant or if duplication is an issue. For example, it may not matter in some cases whether two emails are sent during the evaluation. Another option is to have the new code determine the result but not persist it. This would prevent any meaningful performance comparisons. However, it would allow you to verify accuracy.&lt;/p&gt;

&lt;p&gt;Other limitations stem from Scientist’s focus on return values. In some cases, valid results may exhibit differences over time, whether they simply include timestamps in the response or certain factors vary. In many cases, we can write custom comparison logic in the experiment to verify accuracy beyond basic string comparisons.&lt;/p&gt;

&lt;p&gt;Finally, a limitation of LabTech is that it has not been ported to Rails 7 as of the time of writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Effective Scientist Experiments in Rails
&lt;/h2&gt;

&lt;p&gt;Consider these items when implementing your experiments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Rails projects, Scientist can either be configured in an initializer or a wrapper like the Rails LabTech gem. Most Rails applications already have a database, so LabTech leverages ActiveRecord to store results.&lt;/li&gt;
&lt;li&gt;To avoid slowing down development and testing, enable your experiment only in staging and production environments.&lt;/li&gt;
&lt;li&gt;To minimize any potential impact on production, only run the experiment on a percentage of requests. LabTech supports this out of the box as an optional parameter when you enable the experiment (it is initially disabled by default). Using pure Scientist, this logic is easy to code in the experiment’s &lt;code&gt;enabled?&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;Some logic is resource-intensive, so a low sampling rate may be a good place to start. As you gain confidence with the results, ramp up the percentage of requests being evaluated.&lt;/li&gt;
&lt;li&gt;You can add context attributes to get the most from your results. The experiment context can be set to a Symbol-keyed Hash of data that is then made available in published results, e.g.:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;experiment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;context&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrap-Up: Observe and Monitor Your Ruby App with Scientist
&lt;/h2&gt;

&lt;p&gt;In this post, we explored how to use the Scientist gem to change, migrate, and refactor Ruby code in production.&lt;/p&gt;

&lt;p&gt;We examined Scientist's origins in the Branch by Abstraction pattern, then dived into refactoring. Next, we saw how LabTech could help with gathering results and your Scientist configuration.&lt;/p&gt;

&lt;p&gt;We then touched on some limitations of Scientist before finally outlining a few best practices.&lt;/p&gt;

&lt;p&gt;You must observe and monitor what is happening in your system. Integrate Scientist into your development process to make critical changes in your Ruby code with greater confidence.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
  </channel>
</rss>
