<?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>DEV Community: Daniel</title>
    <description>The latest articles on DEV Community by Daniel (@danldevio).</description>
    <link>https://dev.to/danldevio</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%2F991023%2F99c0b5ff-746b-4d43-9ac5-a7cde8b5cbde.jpeg</url>
      <title>DEV Community: Daniel</title>
      <link>https://dev.to/danldevio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danldevio"/>
    <language>en</language>
    <item>
      <title>Optimize Database Performance in Ruby on Rails and ActiveRecord</title>
      <dc:creator>Daniel</dc:creator>
      <pubDate>Wed, 20 Nov 2024 13:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/optimize-database-performance-in-ruby-on-rails-and-activerecord-mcn</link>
      <guid>https://dev.to/appsignal/optimize-database-performance-in-ruby-on-rails-and-activerecord-mcn</guid>
      <description>&lt;p&gt;In Rails, we're more likely to use SQL databases than other frameworks. Unlike NoSQL databases, which can be scaled horizontally with relative ease, SQL databases like PostgreSQL or MySQL are much less amenable to easy scaling.&lt;/p&gt;

&lt;p&gt;As a result, our database usually becomes the primary bottleneck as our business grows. Although SQL databases are very efficient, as our growing customer base puts an increasing load on our servers, we begin scaling our instance counts, workers, etc. But we can't just make copies of our database for each new server we spin up. This makes optimizing database performance critical for any serious Rails project.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore strategies for optimizing performance to minimize the load on our database. We'll start with some more basic topics like eager loading and the N+1 query problem, database indexing, &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;pluck&lt;/code&gt;, and immediate loading. Then, we'll move on to more involved topics like performance profiling, scaling via techniques like database sharding, and background jobs using read replicas.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Getting ActiveRecord N+1s Out of the Way
&lt;/h2&gt;

&lt;p&gt;No discussion of database performance involving ActiveRecord (or any ORM) is complete without addressing the &lt;a href="https://blog.appsignal.com/2018/04/24/active-record-performance-the-n-1-queries-antipattern.html" rel="noopener noreferrer"&gt;infamous N+1 problem&lt;/a&gt;. While most readers are likely familiar with it, it's still worth revisiting, as N+1 queries can (and do) creep into our projects over time, especially as our codebase evolves and we add or update features.&lt;/p&gt;

&lt;p&gt;The N+1 problem generally occurs when iterating through a (potentially large) group of retrieved records. Because we haven't loaded their association/s, each loop means an additional query for each associated model we access inside the loop. This can quickly spiral out of control, resulting in a staggering number of queries and causing serious consequences such as crashed pages, an exhausted database connection pool, and memory running out, ultimately grinding our site to a halt.&lt;/p&gt;

&lt;p&gt;There are a few useful tools at your disposal to help identify and resolve N+1 problems. The first is simply looking at your server output; generally, this works pretty well, as N+1s are easy to spot. For a more assisted approach, the &lt;a href="https://github.com/flyerhzm/bullet" rel="noopener noreferrer"&gt;&lt;code&gt;Bullet&lt;/code&gt;&lt;/a&gt; gem is a popular tool that automatically detects N+1s in applications and suggests ways to fix them. Another, arguably better option is &lt;a href="https://github.com/charkost/prosopite" rel="noopener noreferrer"&gt;&lt;code&gt;prosopite&lt;/code&gt;&lt;/a&gt;, a less well-known option that generally provides better results with fewer false positives (and false negatives).&lt;/p&gt;

&lt;p&gt;Eager loading should be used with care, however; while you're probably safe loading &lt;code&gt;has_one&lt;/code&gt; associations, &lt;code&gt;has_many&lt;/code&gt; can sometimes be dangerous: what happens when we've got a query of multiple joined, &lt;a href="https://en.wikipedia.org/wiki/Many-to-many_(data_model)" rel="noopener noreferrer"&gt;many-to-many&lt;/a&gt; tables, and eager load one or more of the many-to-many associations?&lt;/p&gt;

&lt;p&gt;We can end up loading &lt;em&gt;way&lt;/em&gt; too many records into memory. This can crash our app just as surely as a bad N+1. If you're at this point, and both the N+1 and eager loading approaches are causing you problems, it may be time to reevaluate the query itself. Maybe you're trying to load associations you just want to &lt;code&gt;COUNT&lt;/code&gt; (which is something you might be able to build into your query), tighten your pagination limits, or consider offloading the query to a background job if possible (which we'll cover).&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Performance in Rails: Considerations and Optimization
&lt;/h2&gt;

&lt;p&gt;Sometimes, breaking queries down into a couple of steps can both improve performance and simplify a query. Ever find yourself trying to get a subset of records to complete a mind-bending query? In certain cases, just finding the IDs of the subset you want and then feeding them into a second, less complex query can help reduce the need for complex joins, subqueries, etc.&lt;/p&gt;

&lt;p&gt;Have you ever fallen victim to Rails' (usually beneficial) default lazy-loading and evaluation, and wished you could load something &lt;em&gt;immediately&lt;/em&gt; rather than the first time it's used? Sometimes we need not only the results of a query, but another aspect of it too, like the number of records we've retrieved. Our seemingly harmless code looks like 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="vi"&gt;@users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"email ILIKE ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="vi"&gt;@total_users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, this is fine, but what if we need to count users before we do anything with them (maybe we're displaying the number of users returned in our live search at the top of the page, followed by the users themselves)? We might add &lt;code&gt;COUNT&lt;/code&gt; to our query, but then we need to retrieve the records again.&lt;/p&gt;

&lt;p&gt;We can get around this by explicitly calling &lt;code&gt;load&lt;/code&gt; on our query — for example:&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="vi"&gt;@users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"email ILIKE ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;
&lt;span class="vi"&gt;@total_users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple change can save us a query. With our users already loaded, our display of &lt;code&gt;@total_users&lt;/code&gt; above the user list will no longer trigger an expensive &lt;code&gt;COUNT&lt;/code&gt; followed by another query to get users.&lt;/p&gt;

&lt;p&gt;In the other direction, we have &lt;a href="https://www.rubydoc.info/github/rails/rails/ActiveRecord%2FRelation:load_async" rel="noopener noreferrer"&gt;&lt;code&gt;load_async&lt;/code&gt;&lt;/a&gt; with Rails 7. This allows us to asynchronously query the database with multiple requests at once, rather than being locked to one synchronous query at a time. This can be a total game-changer if you're in a situation that can benefit from it. Be careful though, as this (probably unsurprisingly) uses Ruby threads under the hood, so comes with all the same issues you'd expect from threads. That's not to mention the fact that it (potentially) opens our database up to a greatly increased number of connections, and can exhaust our connection pool if we aren't careful.&lt;/p&gt;

&lt;p&gt;A large part of database performance in Rails comes down to how you utilize ActiveRecord. While AR is an invaluable tool, it can also lead to performance issues if not used with care. As you're probably aware, it loads every column of every table involved in a query (&lt;code&gt;SELECT *&lt;/code&gt;) by default, regardless of how much of that data we actually use. It's pretty easy to forget this though, especially as our queries evolve, and we can end up loading a lot of unnecessary data. It's important to consider whether or not you're really using all of the columns you're loading, or if a &lt;code&gt;select&lt;/code&gt; (or even a &lt;code&gt;pluck&lt;/code&gt;) statement might be a better fit for your use case.&lt;/p&gt;

&lt;p&gt;You're probably familiar with eager loading in Rails, but one thing that's often overlooked is that Rails makes it easy to introduce &lt;em&gt;optional&lt;/em&gt; eager-loading (and &lt;code&gt;WHERE&lt;/code&gt; clauses, and &lt;code&gt;SELECT&lt;/code&gt;s). If you've ever had slightly different requirements for two very similar queries, you might feel like you're left with two options: write two queries, or write covering both use cases, each retrieving more than you need. This might happen in a &lt;code&gt;before_action&lt;/code&gt;, where you typically want the same thing for each action in a particular controller. But one controller might benefit from eager loading, more careful column selection, or a search results page which is just filtering the usual index based on some criteria.&lt;/p&gt;

&lt;p&gt;There's another way, though:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_user_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;before_date: &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;optional_selects: &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;optional_eager_loads: &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;exclude_inactive: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;search: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;optional_before_date_constraint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"created_at &amp;lt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;before_date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;before_date&lt;/span&gt;
    &lt;span class="n"&gt;optional_joins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;user_posts: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;posts: :comments&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;

    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optional_eager_loads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:shared_joins&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optional_joins&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:columns_shared_by_queries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optional_selects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"your shared WHERE constraints"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optional_before_date_constraint&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;Rails is smart enough to know not to execute anything here if there's nothing passed in; &lt;code&gt;where(nil)&lt;/code&gt; will behave as though it doesn't exist, as will &lt;code&gt;includes([])&lt;/code&gt;, etc. This can really clean up your code and help you tailor performance to your needs.&lt;/p&gt;

&lt;p&gt;Finally, rethinking your &lt;code&gt;JOIN&lt;/code&gt;s and &lt;code&gt;WHERE&lt;/code&gt;s can sometimes be beneficial. Are you querying a very large dataset where using a &lt;code&gt;JOIN&lt;/code&gt; could significantly reduce the initial result set? Try it out, and don't be afraid to run &lt;a href="https://apidock.com/rails/ActiveRecord/Relation/explain" rel="noopener noreferrer"&gt;&lt;code&gt;#explain&lt;/code&gt;&lt;/a&gt; on your query. It can help you determine if switching to a &lt;code&gt;JOIN&lt;/code&gt; might work better and identify any missing indexes on critical queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Indexing
&lt;/h2&gt;

&lt;p&gt;One of our most important (and nearly universally necessary) tools for database performance is indexing. As you're probably aware, indexes are special data structures (typically &lt;a href="https://en.wikipedia.org/wiki/B-tree" rel="noopener noreferrer"&gt;B-trees&lt;/a&gt;) that a database uses to quickly find records, improving retrieval times from &lt;code&gt;O(n)&lt;/code&gt; to &lt;code&gt;O(log(n))&lt;/code&gt;. However, it's important to add indexes carefully, as indexing columns unnecessarily can actually &lt;a href="https://www.arkware.com/striking-a-balance-the-pitfalls-of-over-indexing-in-databases/" rel="noopener noreferrer"&gt;harm performance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The trick is to identify the columns and associations that your application needs to query frequently, and then create indexes on those. This allows the database to quickly locate the relevant data without having to scan the entire table.&lt;/p&gt;

&lt;p&gt;You can also use partial indexing. Partial indexes allow you to index a subset of a table's rows based on a specified condition. This is particularly useful when your queries frequently target only a specific portion of the data.&lt;/p&gt;

&lt;p&gt;For example, maybe your app has a core of frequent visitors, the majority of whom have their own accounts. They make up the lion's share of your traffic, but represent a small minority of your total users. You could define a partial index on the &lt;code&gt;users&lt;/code&gt; table like 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;AddPartialIndexToUsersOnGuest&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;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.1&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;change&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:guest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where: &lt;/span&gt;&lt;span class="s2"&gt;"(guest = false)"&lt;/span&gt; &lt;span class="c1"&gt;# the `where: condition` here makes this a partial index.&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;Here, we've specifically targeted rows where &lt;code&gt;guest&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;. This optimizes the performance of any query searching for non-guest &lt;code&gt;users&lt;/code&gt; by reducing the total rows covered by the index.&lt;/p&gt;

&lt;p&gt;It's important to revisit your indexing profile and database schema periodically. The specific indexing needs of your application may change over time as the user base, codebase, and feature set evolve. Doing so often means you end up reviewing your indexes during comfortable periods where you have time to analyze and think about your database, rather than in a panic when an influx of traffic crashes your app.&lt;/p&gt;

&lt;p&gt;All of this brings us to our next topic: performance profiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Profiling for Your Ruby on Rails App
&lt;/h2&gt;

&lt;p&gt;So, we've identified some major categories of performance optimization; what now? Do we go through our entire app from top to bottom, optimizing line by line? Probably not.&lt;/p&gt;

&lt;p&gt;It's pretty unlikely that you have evenly distributed traffic across your whole site. It's even less likely that you can afford to go through your entire codebase; premature optimization may not actually be the root of all evil, but you also don't have time for it. So how do we know what to focus on?&lt;/p&gt;

&lt;p&gt;This is where profiling tools come in. While your local server output can provide valuable performance insights into response times, memory use, and query performance, you are only one user. Your local database likely doesn't reflect the scale or patterns of your production database. Valuable as it is for spotting potential performance issues, your local environment doesn't provide the context you need to make decisions about what genuinely needs your attention.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.appsignal.com/learning-center/what-is-an-apm" rel="noopener noreferrer"&gt;Application Performance Monitoring tools&lt;/a&gt; (APMs) like &lt;a href="https://www.appsignal.com/ruby" rel="noopener noreferrer"&gt;AppSignal for Ruby&lt;/a&gt; are essential for monitoring and maintaining our site's performance. They provide comprehensive insights not just at the database layer, but across the rest of the stack, allowing us to pinpoint specific areas (pages, endpoints, and even specific queries) that are causing headaches. With a good APM tool, we can track the performance of any request over various timescales, monitor background jobs, spot memory leaks, set up custom error and performance alerts, and more.&lt;/p&gt;

&lt;p&gt;APMs provide visualization tools that make it much easier to spot and understand complex performance trends, leading to quicker, more informed decisions. They help prioritize optimization efforts by highlighting not only the slowest queries and most resource-intensive parts of an application, but also the endpoints that consume the most total time (&lt;code&gt;total request count * average response time&lt;/code&gt;). This means we can focus on what's actually important. Remember that page we were worried about that takes a couple of seconds to load? Turns out, its total impact on our site amounts to practically nothing. Our products page though, clocking in at a reasonable 150ms? That's taking up 15% of our total server time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To find out more about measuring database performance using AppSignal, check out our post &lt;a href="https://blog.appsignal.com/2024/06/12/monitor-the-performance-of-your-ruby-on-rails-application-using-appsignal.html" rel="noopener noreferrer"&gt;Monitor the Performance of Your Ruby on Rails Application Using AppSignal&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background Jobs
&lt;/h2&gt;

&lt;p&gt;It isn't always an option, but running queries in background jobs is a powerful tool for improving performance.&lt;/p&gt;

&lt;p&gt;You might be asking yourself how this could be, given that we only have one database; what difference does it make if we're merely calling on it from another thread or process? But there are a few main ways background jobs can come to our database's rescue.&lt;/p&gt;

&lt;p&gt;First, just by nature of the fact that our background jobs run asynchronously, we're pretty free to run relatively resource-intensive tasks — like setting up &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-insert_all" rel="noopener noreferrer"&gt;bulk inserts&lt;/a&gt; (or &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-upsert_all" rel="noopener noreferrer"&gt;upserts&lt;/a&gt;), for example (note: neither validations nor callbacks are triggered for bulk operations) — and potentially distributing large numbers of queries across periods of time (perhaps especially for write operations), possibly with the use of &lt;code&gt;find_each&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You're likely used to using a job to process a CSV or spreadsheet; those tasks tend to lend themselves to asynchronous processing as well as bulk insertions. If you're doing this on your server's main thread, you're likely doing something wrong. But sometimes other, less obvious things we're currently doing on our main thread can be moved to a background job to improve performance on the main thread &lt;em&gt;and&lt;/em&gt; reduce load on the database.&lt;/p&gt;

&lt;p&gt;Consider a scenario where your site allows people to make potentially large purchases of relatively inexpensive items. A school, for example, might order tens of thousands of pencils at once, and maybe your system creates a &lt;code&gt;PurchaseItem&lt;/code&gt; for every single item purchased. Instead of creating or updating thousands of rows one at a time, we can set up a bulk insert in a background job, create all the individual hashes inside a loop, and use &lt;code&gt;insert_all&lt;/code&gt; to get the job done — taking advantage of our background worker to set up our bulk inserts, and then writing to the database once, instead of locking it with thousands of writes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read Replicas
&lt;/h2&gt;

&lt;p&gt;Background jobs are great, but eventually, even if we're spacing out those large, numerous database queries, we hit a point where we're asking too much of our database. We're probably okay in terms of write performance (we tend to read from our database far more than we write to it, after all), but we're constantly bombarding it with queries. Our site's response times are steadily rising, and user experience is suffering.&lt;/p&gt;

&lt;p&gt;At this point, the next logical step may be to introduce &lt;a href="https://blogs.oracle.com/mysql/post/read-replicas-mysql-database-service" rel="noopener noreferrer"&gt;read replicas&lt;/a&gt; to offload work from the primary database. Read replicas allow you to distribute the read query load across one or more replicas of your database. This can improve performance across the board, as it greatly reduces the number of reads (and thus the total I/O) of your primary database, leaving it less swamped by requests (most of which are likely reads in the first place).&lt;/p&gt;

&lt;p&gt;Ideally, large database operations are performed in background jobs reading from replicas, setting up a bulk &lt;code&gt;insert&lt;/code&gt; or &lt;code&gt;upsert&lt;/code&gt;, and then making one atomic write to your main database. This means you can potentially raise not only the read performance of your site, but can also boost write performance to some degree, as your primary database is consistently freer to accept write requests.&lt;/p&gt;

&lt;p&gt;While &lt;a href="https://catalinionescu.dev/blog/how-to-connect-to-multiple-databases/" rel="noopener noreferrer"&gt;read replicas are not painless to set up&lt;/a&gt;, they can provide the extra performance our site needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Sharding At A High Level
&lt;/h2&gt;

&lt;p&gt;Remember how we said SQL databases don't scale horizontally? Technically, they can scale horizontally through &lt;a href="https://blog.appsignal.com/2022/12/07/database-performance-optimization-and-scaling-in-rails.html" rel="noopener noreferrer"&gt;database sharding&lt;/a&gt;. Sharding involves splitting up your database into multiple databases, which are then distributed across multiple servers (and often across geographical locations).&lt;/p&gt;

&lt;p&gt;There are several approaches for doing this, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Geographical sharding (based on user locations).&lt;/li&gt;
&lt;li&gt;Vertical sharding (splitting into different tables or groups of tables based on access patterns, with each shard storing a different subset of the database’s tables).&lt;/li&gt;
&lt;li&gt;Functional sharding (splitting based on business function; like separating out billing from user content).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more. Each approach has its use cases, and some naturally lend themselves better to some businesses than others.&lt;/p&gt;

&lt;p&gt;As you've doubtlessly realized by now, sharding is generally difficult, and depending on your business, sometimes not even possible. It adds complexity, developmental and maintenance overhead to our apps, and potentially a lot of it. The good news is that if you don't have the resources to cover this, you probably don't need it in the first place. The bad news is that even if you do, it still complicates things and means increased costs (presumably balanced by the potential benefits).&lt;/p&gt;

&lt;p&gt;But if you've done all you can with query optimization, read replicas, and caching, sharding might just be the next necessary step — and one that can greatly increase your I/O capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Words of Caution and Final Words: Database Optimization in Ruby on Rails
&lt;/h2&gt;

&lt;p&gt;We need to be conscientious in how we approach our optimizations. It's possible to over-index (or index the wrong things, which hurts write performance). In the same way that N+1s can get out of control if we're not careful, so can eager loading, and it's not always immediately obvious when we're developing locally (hence the importance of at least approximating your real-world site locally).&lt;/p&gt;

&lt;p&gt;Keep an eye on things via an APM, as traffic and user patterns can change and new ones can emerge, not just over time but potentially week to week and season to season, depending on your business.&lt;/p&gt;

&lt;p&gt;There's no one-size-fits-all approach, so we have to be mindful of how we tailor our optimizations to our own site. Remember not to waste too much time trying to optimize things until you have an idea of the impact they'll have on your site.&lt;/p&gt;

&lt;p&gt;That doesn't mean you shouldn't try to write generally efficient code, just that there's generally no reason to agonize over tweaking things to be absolutely optimal or wasting time on micro-optimizations. Try to keep the time-to-benefit ratio in mind, and how what you're working on will likely fit into your existing site as it is now, and as it evolves.&lt;/p&gt;

&lt;p&gt;If you're interested in diving deeper into aspects of database optimization like sharding, you can &lt;a href="https://edgeguides.rubyonrails.org/active_record_multiple_databases.html" rel="noopener noreferrer"&gt;learn more from the Rails guides&lt;/a&gt;. Also, check out &lt;a href="https://pawelurbanek.com/rails-load-async" rel="noopener noreferrer"&gt;Paweł Urbanek's great article on &lt;code&gt;load_async&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy optimizing!&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" rel="noopener noreferrer"&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>Expressive Ruby and Rails: Communicate Effectively With Your Code</title>
      <dc:creator>Daniel</dc:creator>
      <pubDate>Wed, 18 Oct 2023 14:38:36 +0000</pubDate>
      <link>https://dev.to/appsignal/expressive-ruby-and-rails-communicate-effectively-with-your-code-4o78</link>
      <guid>https://dev.to/appsignal/expressive-ruby-and-rails-communicate-effectively-with-your-code-4o78</guid>
      <description>&lt;p&gt;Ruby is an expressive language. This is no accident; Matz very consciously designed Ruby as an intuitive language to more or less read like English. It's safe to say that he succeeded. Methods are named very carefully, and do what they say they do; they also tend to have inverse methods which do the opposite.&lt;/p&gt;

&lt;p&gt;In this post, we'll look at why expressive code is important and its impact on your productivity as a developer. Then, we'll explore how to best use some of Ruby's methods.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Importance of Expressiveness in Ruby
&lt;/h2&gt;

&lt;p&gt;Expressive code goes beyond readability ("What does this code &lt;em&gt;do?&lt;/em&gt;"), and clearly communicates intent: it clues us into the developer's considerations and concerns, and the edge cases they were accounting for. It makes clear not just &lt;em&gt;what&lt;/em&gt; the code does, but &lt;em&gt;why&lt;/em&gt; it was written how it was.&lt;/p&gt;

&lt;p&gt;Why is this important? It saves us from spending a lot of time familiarizing ourselves with the context of the code we're working on, clues us in to what we should be looking out for when we modify it, and keeps us from potentially bulldozing carefully-coded functionality and measures meant to safeguard our application.&lt;/p&gt;

&lt;p&gt;Ruby takes to heart the principle that good code should be self-documenting; it largely obviates the need for comments. This is likely one of the reasons we often associate commented code in Ruby with bad code: because, in many cases, it is. Working with external APIs or unfamiliar gems is one thing, but generally, for most of our core functionality, writing idiomatic Ruby means we don't need to write (or read) comments. Usually, any time I find myself with comments all over the place, it's a sign I need to refactor.&lt;/p&gt;

&lt;p&gt;Ultimately, expressive code saves us time, and our company money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Impact of Expressive Coding on Productivity
&lt;/h2&gt;

&lt;p&gt;What problems do we face if we aren't conscientious about how we write and structure our code? The answer is at once straightforward and easy to forget in the moment. The most obvious issue — a lack of clarity — in addition to being its own problem, can lead to a host of other headaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Opacity
&lt;/h3&gt;

&lt;p&gt;In addition to being confusing, inexpressive code also turns the thought process of the developer who coded it into something of a black box, rather than a window into their decision-making.&lt;/p&gt;

&lt;p&gt;As touched on above, this can force the developer maintaining our code to delve into the implementation in greater detail than they should to get context. This only serves to derail us from our task at hand, and makes our lives harder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loss of Trust
&lt;/h3&gt;

&lt;p&gt;When a particular feature or area lacks expressiveness, it can be frustrating, but it is also an opportunity to improve our app. But when a lack of expressiveness is a pattern across our entire codebase, we lose faith in our code as a reliable source of truth to communicate the potential values it may be handling.&lt;/p&gt;

&lt;p&gt;This inevitably leads to constant defensive programming, which can serve to further muddy the waters. We may wind up accounting for potential values that aren't even real possibilities, magnifying the existing problem and further eroding trust. In addition, we might add verbosity to our code, and send potentially misleading signals to other developers (and even our future selves) about what we're accounting for and trying to accomplish.&lt;/p&gt;

&lt;p&gt;On the other hand, a lack of trust can lead to bad assumptions when the &lt;em&gt;right&lt;/em&gt; method is actually being used. Consider a scenario where someone has just spent time cleaning up a section of code and spots something like &lt;code&gt;if @user.address.present?&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rather than stopping and thinking: "hey, there must be a reason &lt;code&gt;present?&lt;/code&gt; was used," they might think: "hey, this particular field is optionally set by the user... so either they set it and it's a string, or it wasn't set, so it's &lt;code&gt;nil&lt;/code&gt;." So they change it to &lt;code&gt;if @user.address&lt;/code&gt;. Except, what happens when the user has a value set, and then later removes it and submits the form? We'll now have an empty string (which now, without &lt;code&gt;present?&lt;/code&gt;, evaluates as truthy).&lt;/p&gt;

&lt;h3&gt;
  
  
  Type Issues
&lt;/h3&gt;

&lt;p&gt;Constantly having to check for potential data types and the possibility of &lt;code&gt;nil&lt;/code&gt; values can be genuinely exhausting and &lt;em&gt;really&lt;/em&gt; slow us down. It not only wastes time by forcing us to work out the possible data types of inputs, but it can also break our focus and flow, and add unnecessary cognitive overhead.&lt;/p&gt;

&lt;p&gt;In some ways, we can think of Ruby's expressiveness as an answer to a lack of static typing. By employing clear variable names and self-explanatory methods that do what they say in a fixed, predictable manner, we can automatically infer the type (or potential types) of data we're working on in any given line with little effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintenance and Extensibility Issues
&lt;/h3&gt;

&lt;p&gt;Inexpressive code can be a nightmare to maintain, and it often leads to tangled code, quickly becoming tough to modify without breaking something. It's even harder to extend with new features and functionality, and can become a bug-prone black box that's slow and challenging to develop for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hurting the Bottom Line
&lt;/h3&gt;

&lt;p&gt;We pile up technical debt, which costs us increasingly painful amounts of time, loses our business money, and potentially hurts our reputation if this leads to missed deadlines or buggy releases.&lt;/p&gt;

&lt;p&gt;On top of all this, the less expressive our code is, the more painful turnover becomes; onboarding new developers takes longer, as it will be a while before they're productive and know the codebase, and losing developers who do know it has a greater impact. And, of course, even when they're up and running, they and everyone else working on the application will simply be less productive than they could be.&lt;/p&gt;

&lt;p&gt;Now, let's focus on how we should best use methods in Ruby and Rails to avoid some of these issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing Similar Ruby and Rails Methods
&lt;/h2&gt;

&lt;p&gt;As we touched on early in this post, Ruby and Rails come together to offer us a wide variety of methods that accomplish an array of everyday tasks and answer basic questions we might need to ask as programmers. At first glance, there might appear to be a lot of overlap, and we may think that many of these methods can simply be used interchangeably without issue.&lt;/p&gt;

&lt;p&gt;Of course, as we know, these methods were named very carefully. Both Ruby and Rails were designed with close attention to what these methods do and don't do.&lt;/p&gt;

&lt;p&gt;As a result, methods (excluding aliases) each behave slightly differently, with different intended purposes. Their correct use can help us provide specific context to clue in anyone reading our code to what's going on. Conversely, failing to do so can have the opposite effect. Let's take a look at some methods that sometimes get used interchangeably, to our detriment — starting with &lt;code&gt;nil?&lt;/code&gt;, &lt;code&gt;empty?&lt;/code&gt;, &lt;code&gt;blank?&lt;/code&gt;, and &lt;code&gt;none?&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;nil?&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This method does exactly what you'd expect: it tells us whether a value is &lt;code&gt;nil&lt;/code&gt;. I generally like to use it to avoid doing things like &lt;code&gt;!@user&lt;/code&gt;, but it can also indicate that a value is &lt;em&gt;not&lt;/em&gt; a boolean, if that's not already clear. While it's difficult to misuse, it's easy to use another, technically valid but misleading method (such as &lt;code&gt;blank?&lt;/code&gt;) in its place. Don't do this!&lt;/p&gt;

&lt;p&gt;If a value can only be &lt;code&gt;nil&lt;/code&gt; or truthy, &lt;code&gt;nil?&lt;/code&gt; should always be used. Doing otherwise can mislead anyone modifying the code and potentially lead them to code for non-existent edge cases, which is both a waste of time, and adds unnecessary complexity and verbosity.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;empty?&lt;/code&gt; (Nearly Opposite Method: &lt;code&gt;any?&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;empty?&lt;/code&gt; is useful when you want to check whether a collection array, string, hash, or other type of collection contains no elements. It's a straightforward and intuitive method that returns &lt;code&gt;true&lt;/code&gt; if the collection contains no elements, and &lt;code&gt;false&lt;/code&gt; otherwise.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;"".empty?&lt;/code&gt; and &lt;code&gt;[].empty?&lt;/code&gt; both return &lt;code&gt;true&lt;/code&gt;, because the string and the array do not contain any elements. Similarly, &lt;code&gt;{}.empty?&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; because the hash does not contain any key-value pairs.&lt;/p&gt;

&lt;p&gt;When you use &lt;code&gt;empty?&lt;/code&gt;, you're communicating to other developers that the object you're checking should be a string or collection of some sort, and you're interested in whether it contains anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;blank?&lt;/code&gt; (Opposite Method: &lt;code&gt;present?&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://apidock.com/rails/Object/blank%3F"&gt;&lt;code&gt;blank?&lt;/code&gt;&lt;/a&gt; is often used to check if a string is empty. However, as you're likely aware, it checks for more than just empty strings; it will return &lt;code&gt;true&lt;/code&gt; if a value is &lt;code&gt;nil&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, or an empty array or hash. Essentially, it asks and answers &lt;code&gt;!object || object.empty?&lt;/code&gt;, so we expect that the potential values are probably either &lt;code&gt;nil&lt;/code&gt;, or an empty string, array, or hash — this is what you're communicating to other developers when you use this.&lt;/p&gt;

&lt;p&gt;It is often used in place of &lt;code&gt;empty?&lt;/code&gt; or &lt;code&gt;nil?&lt;/code&gt; when it really shouldn't be.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;none?&lt;/code&gt; (Opposite Method: &lt;code&gt;all?&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This asks whether an array or hash is empty or only contains falsy values, and it's not too often misused. Sometimes, though, we might see someone use &lt;code&gt;blank?&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;This is a mistake. In addition to the fact that &lt;code&gt;blank?&lt;/code&gt; asks: "is this object falsy or empty?", &lt;code&gt;none?&lt;/code&gt; asks not only: "is this array or hash empty?", but also: "...or does this array have no truthy values?". &lt;code&gt;[false, nil].blank?&lt;/code&gt;, for example, will return &lt;code&gt;false&lt;/code&gt;, but &lt;code&gt;[false, nil].none?&lt;/code&gt; will return &lt;code&gt;true&lt;/code&gt; (likewise, &lt;code&gt;any?&lt;/code&gt; would return &lt;code&gt;false&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So not only have we introduced ambiguity, we've introduced a bug waiting to happen — and sadly, this one is not a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;size&lt;/code&gt; Vs. &lt;code&gt;length&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;size&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Put simply: &lt;a href="https://apidock.com/rails/v6.1.3.1/ActiveRecord/Associations/CollectionProxy/size"&gt;&lt;code&gt;size&lt;/code&gt;&lt;/a&gt; is for collections (ActiveRecord Collections, arrays), and should almost always be used for them. It never makes sense to call on a string.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;length&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;length&lt;/code&gt; is generally for strings, and except in specific cases, shouldn't be used for collections (and probably never for arrays).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;boolean?&lt;/code&gt; Methods in Ruby
&lt;/h3&gt;

&lt;p&gt;Every Ruby &lt;code&gt;boolean?&lt;/code&gt; method answers a yes or no question: "Is this value truthy or not?". Most Rails developers are aware of this and generally use the question mark to good effect, adding it to the end of the boolean methods that they write. But many may not be aware that Rails itself does, too.&lt;/p&gt;

&lt;p&gt;Any Rails object automatically responds to a column with a &lt;code&gt;?&lt;/code&gt; on the end. So, a &lt;code&gt;verified&lt;/code&gt; column on your user table can be called with &lt;code&gt;@user.verified?&lt;/code&gt;. Setting up a standard for your team to only use these methods for &lt;code&gt;boolean&lt;/code&gt; columns can help provide even more context for other (especially new) team members. They won't have to check the schema to see if &lt;code&gt;verified&lt;/code&gt; is maybe a &lt;code&gt;timestamp&lt;/code&gt; or even a &lt;code&gt;string&lt;/code&gt; rather than a &lt;code&gt;boolean&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And let's not forget the methods we write ourselves; generally speaking, any method we write that answers a yes-or-no question should return a boolean method, and end with a question mark.&lt;/p&gt;

&lt;p&gt;For example, say we have a method that tells us whether or not a value is outside a particular limit. We can define something like 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;Vendor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;use_exceeds_limit?&lt;/span&gt;
    &lt;span class="n"&gt;get_time_slot_use_count&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="nf"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;time_slot_tier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then conditionally email to let users who have exceeded the limit know they'll be charged at a higher rate for the month:&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;DailyLimitCheckJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;VendorMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alert_vendor_limit_exceeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_exceeds_limit?&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;So, great, we know that methods that end in a &lt;code&gt;?&lt;/code&gt; return a boolean. But what about other methods?&lt;/p&gt;

&lt;p&gt;Note the method &lt;code&gt;get_time_slot_use_count&lt;/code&gt;. It may be slightly more explicit than it needs to be, but it also tells us exactly what we're getting: an &lt;code&gt;Integer&lt;/code&gt;. If it were &lt;code&gt;get_time_slot_usage&lt;/code&gt;, we might wonder if it wasn't a &lt;code&gt;Float&lt;/code&gt;, or even an &lt;code&gt;ActiveSupport::Duration&lt;/code&gt; (with a return value like &lt;code&gt;173.hours&lt;/code&gt;). Being explicit helps us know what we'll get without looking at the actual implementation elsewhere in the &lt;code&gt;Vendor&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;So, we can also apply this principle elsewhere: we probably don't need it for strings like &lt;code&gt;name&lt;/code&gt; or &lt;code&gt;title&lt;/code&gt;, but if we're assigning variables to dynamically hold parts of a query, we probably do want to name it something like &lt;code&gt;query_string&lt;/code&gt;, rather than &lt;code&gt;query&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same is true of arrays and hashes: generally, a plural name (say, &lt;code&gt;products&lt;/code&gt;) indicates that we're dealing with a collection. We often don't need to explicitly specify "hash", either; consider &lt;code&gt;pagination_options&lt;/code&gt;. We know options in contexts like these are typically hashes. But sometimes we have &lt;code&gt;products&lt;/code&gt; as a key-value pair collection; we should probably name this &lt;code&gt;products_hash&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Resources
&lt;/h2&gt;

&lt;p&gt;For those seeking a deeper dive into the world of Ruby, a number of resources are available.&lt;/p&gt;

&lt;p&gt;Check out the books &lt;a href="https://www.amazon.co.uk/Ruby-Programming-Language-David-Flanagan/dp/0596516177"&gt;The Ruby Programming Language&lt;/a&gt; and &lt;a href="https://www.amazon.co.uk/Ruby-Best-Practices-Gregory-Brown/dp/0596523009"&gt;Ruby Best Practices&lt;/a&gt;, the &lt;a href="https://ruby-doc.org/"&gt;official Ruby docs&lt;/a&gt;, and &lt;a href="https://guides.rubyonrails.org/"&gt;Rails Guides&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;In this post, we've walked through the pitfalls of inexpressive code, including a lack of clarity, a decrease in trust, and other problems. In contrast, well-crafted, expressive code can help avoid issues like technical debt, make our lives easier, and save money.&lt;/p&gt;

&lt;p&gt;The design philosophy of Ruby prioritizes expressiveness and intuitiveness. Properly leveraging the tools Ruby provides to communicate your intent can eliminate the need for extensive comments and lead to substantial savings in both time and resources.&lt;/p&gt;

&lt;p&gt;To fully unlock the power Ruby and Rails affords us, it's critical to be conscientious in your variable and method naming, and to use the right method for the job. Expressive code makes you — and everyone around you — more productive.&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>
      <category>rails</category>
    </item>
    <item>
      <title>An Introduction to Metaprogramming in Ruby</title>
      <dc:creator>Daniel</dc:creator>
      <pubDate>Wed, 02 Aug 2023 14:29:15 +0000</pubDate>
      <link>https://dev.to/appsignal/an-introduction-to-metaprogramming-in-ruby-114e</link>
      <guid>https://dev.to/appsignal/an-introduction-to-metaprogramming-in-ruby-114e</guid>
      <description>&lt;p&gt;You've heard of metaprogramming: code you write that generates other code dynamically. Without it, Rails as we know it wouldn't (and couldn't) exist. But there's a good chance you've never done it yourself, and it's not hard to see why; even a brief excursion into the realm of metaprogramming can leave you beset with strange and foreign methods, unfamiliar syntax, and downright mystifying blocks of code.&lt;/p&gt;

&lt;p&gt;It's true: metaprogramming &lt;em&gt;is&lt;/em&gt; a relatively advanced topic. If you want to &lt;em&gt;really&lt;/em&gt; dig in and leverage it on a deep level, it will take time, effort, and a different way of thinking than you're used to. But there's good news: You don't need to wade too deeply into the metaprogramming waters to discover useful methods and techniques to make your life and workflow a little easier.&lt;/p&gt;

&lt;p&gt;In this post, we'll take a look at metaprogramming methods like &lt;code&gt;send&lt;/code&gt;, &lt;code&gt;define_method&lt;/code&gt;, and &lt;code&gt;method_missing&lt;/code&gt; and show how they can solve problems we sometimes run into, even in normal Rails applications. But first, let's briefly define metaprogramming and explore when it might come in handy.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Is Metaprogramming Useful?
&lt;/h2&gt;

&lt;p&gt;Maybe you've got a couple of models in your app that, while being very similar, aren't identical — they have different attributes and methods. But both need to be acted on in a job, and you don't want to write what is essentially the same code more than once.&lt;/p&gt;

&lt;p&gt;Maybe once (or twice), you hacked together a &lt;del&gt;kludge&lt;/del&gt; masterpiece for a tight deadline on a new feature that got the job done, but you modified an existing model rather than creating a new one the way you would have if you'd had more time. Now that feature needs to be expanded, requiring you to do it the "right" way. This can be intimidating, especially if your codebase references your current implementation all over your app.&lt;/p&gt;

&lt;p&gt;Or maybe you rely on a third-party gem that isn't well-maintained, and you discover far too late that it's got a bug in it — right in the middle of a line of code full of metaprogramming.&lt;/p&gt;

&lt;p&gt;I've had all these things happen, and I solved them by leveraging some light metaprogramming, all without having to be an expert, and without too much effort. You can, too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Metaprogramming in Ruby?
&lt;/h2&gt;

&lt;p&gt;Metaprogramming is a set of techniques that Ruby offers us to write code that dynamically writes other code for us. Rather than working on data, metaprogramming works on other code. This is great because it means we can write more dynamic, flexible, and adaptable code if the situation calls for it.&lt;/p&gt;

&lt;p&gt;It can help DRY up portions of our code, dynamically define methods we might need, and write useful and reusable pieces of software (like gems!) to make our lives easier.&lt;/p&gt;

&lt;p&gt;In fact, Rails leverages metaprogramming on a large scale and to great effect. Any time anyone talks about Rails' magic, they're really talking about its use of metaprogramming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;Despite how useful metaprogramming is, it isn't without its drawbacks.&lt;/p&gt;

&lt;p&gt;One issue is &lt;strong&gt;readability&lt;/strong&gt;: a little metaprogramming here and there can go a long way and likely won't cause many headaches, but lots of it will hurt the readability of your code.&lt;br&gt;
Many Rails engineers probably are not very familiar with it, so relying on it too heavily can prove frustrating both to your future self and to others who need to work on your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintainability&lt;/strong&gt; is also a concern; heavy use of metaprogramming can be mind-bending for even experienced Ruby developers. This means you should &lt;em&gt;document your code&lt;/em&gt;. Ruby is an incredibly expressive, intuitive, and readable language, but if you're doing things others might find confusing, you should leave comments. You should also use access modifiers - e.g., &lt;code&gt;private&lt;/code&gt;, &lt;code&gt;protected&lt;/code&gt; - the way you would with other methods.&lt;/p&gt;

&lt;p&gt;Another potential source of trouble: &lt;strong&gt;monkeypatching&lt;/strong&gt;. While monkeypatching (dynamic modification of a class — typically, a Rails core class) can be useful, it should also be used sparingly and carefully. If we're not mindful, we can modify behavior in ways that, while perhaps solving one of our problems, can easily create dozens of others. For example, rather than fixing a small bug, adding a new, uniquely-named method, or simply extending the capabilities of a class, we might modify the behavior of existing methods used not just by our own application but by our gems (and even Rails itself), with potentially disastrous consequences. Check out our post &lt;a href="https://blog.appsignal.com/2021/08/24/responsible-monkeypatching-in-ruby.html"&gt;Responsible Monkeypatching in Ruby&lt;/a&gt; for more on monkeypatching.&lt;/p&gt;

&lt;p&gt;Lastly, metaprogramming can make it &lt;strong&gt;hard to find things you're looking for&lt;/strong&gt;, especially if you didn't write the code in the first place. If you find yourself trying to figure out how it's possible &lt;code&gt;CompanySpecificClass#our_special_method&lt;/code&gt; exists on an object but isn't defined anywhere, metaprogramming could be the culprit (in this case, probably the use of &lt;code&gt;define_method&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  Basic Ruby Metaprogramming Techniques
&lt;/h2&gt;

&lt;p&gt;Let's explore some of the methods we mentioned earlier: &lt;code&gt;send&lt;/code&gt;, &lt;code&gt;define_method&lt;/code&gt;, and &lt;code&gt;method_missing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, we'll take a look at &lt;code&gt;send&lt;/code&gt;, what it does, and how it can help us DRY up our code.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using &lt;code&gt;send&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Remember the example we mentioned earlier (under the header 'When Is Metaprogramming Useful?') of a couple of similar models with different attributes and methods? In this case, both models need to be acted on in a job in a more or less identical manner.&lt;/p&gt;

&lt;p&gt;Essentially, invoking &lt;code&gt;.send&lt;/code&gt; allows us to dynamically pass methods (along with arguments) to an object, without necessarily knowing what that object (or method) might be at the time we write the code.&lt;/p&gt;

&lt;p&gt;First, a word of caution: &lt;code&gt;send&lt;/code&gt; can actually call both public and private methods. If you want to be very explicit (and careful), you can use &lt;code&gt;public_send&lt;/code&gt; to ensure it's clear you're calling a public method. I've never done this, but I've also never used it to call any &lt;code&gt;private&lt;/code&gt; or &lt;code&gt;protected&lt;/code&gt; methods; if you do, you might want to use &lt;code&gt;public_send&lt;/code&gt; and &lt;code&gt;send&lt;/code&gt; for each use case.&lt;/p&gt;

&lt;p&gt;So how does it work? Pretty simply. You just pass the method's name to the object you want to call like this: &lt;code&gt;my_object.send(:method_name)&lt;/code&gt;. If you need to pass parameters/arguments, you can do so: &lt;code&gt;my_object.send(:method_name, argument1, argument2...)&lt;/code&gt;. &lt;code&gt;send&lt;/code&gt; accepts parameters the same way other methods do, so you can use named arguments, too.&lt;/p&gt;

&lt;p&gt;So rather than doing something verbose like 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;def&lt;/span&gt; &lt;span class="nf"&gt;sell_or_refund_or_return_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"sell"&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sell&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"refund"&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refund&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"void"&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;void&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"return"&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;return&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;We can simply pass our method as an argument and call it with &lt;code&gt;send&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&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;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: For the sake of simplicity and brevity, we're not raising an error if our object doesn't &lt;code&gt;respond_to?&lt;/code&gt; the action passed into our above methods. But in a real app, we'd probably want to raise an error to bring our attention to the fact something in our codebase is calling a non-existent method/attribute on the object.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;send&lt;/code&gt;: An Example Scenario
&lt;/h3&gt;

&lt;p&gt;Let's consider the following example. We've got a &lt;code&gt;Purchase&lt;/code&gt; model, representing a given purchase from an online store. That purchase could be anything from a single item of one type to many different items.&lt;/p&gt;

&lt;p&gt;In the instance below, we're interested in people who bought a particular subscription, which may contain tickets to events (&lt;code&gt;purchase_events&lt;/code&gt;), and/or vouchers for products (&lt;code&gt;purchase_products&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Products and events are different things, but there's a lot of overlap, like &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt; (sold, returned), etc. In this example, we will refund all instances of a particular item from a subscription, because customers were unable to redeem their purchase (an event was rained off, a company we source from ran out of a collectible, etc.).&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;RefundAllInstancesOfItemJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;purchases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:subscriptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:purchase_events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:purchase_products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;subscriptions: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscription_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;association&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;foreign_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;foreign_key_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PurchaseEvent&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="ss"&gt;:purchase_events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;event_id&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="ss"&gt;:purchase_products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;purchases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_each&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;purchase&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;association&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;purchase_id: &lt;/span&gt;&lt;span class="n"&gt;purchase&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foreign_key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foreign_key_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"refunded"&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What have we done here? Ordinarily, we might have ended up writing some if/else logic and duplicating most of this code or even two separate jobs. Instead, we've dynamically sent the appropriate association and foreign key to objects within our query, keeping our code DRY.&lt;/p&gt;

&lt;p&gt;We've laid out the keys and associations here explicitly, but they could be passed in as arguments if we set up our call to the job differently. Our customers have now been refunded for the particular item(s) in this subscription cycle! (Let's assume there's a callback on those models that refunds the user when the item is marked &lt;code&gt;"refunded"&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;define_method&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Now, let's look at &lt;code&gt;define_method&lt;/code&gt; and how it can help us with our earlier example where we moved over from storing data on one model where it didn't really belong to a new dedicated model.&lt;/p&gt;

&lt;p&gt;Let's say we currently have a &lt;code&gt;Presentation&lt;/code&gt; model. When we first added video capabilities to our app, we did it because our biggest customer had to play a single, long video about their business to their shareholders. We had a tight deadline, so rather than create a new, dedicated model, we just tacked on a few columns to our &lt;code&gt;Presentation&lt;/code&gt; table, and it got the job done.&lt;/p&gt;

&lt;p&gt;Now, though, because we've advertised our app's recording and streaming capabilities, other clients are interested — and they need more than one video per &lt;code&gt;Presentation&lt;/code&gt;. Unfortunately, we're still in a bit of a time crunch and have to roll this out fast.&lt;/p&gt;

&lt;p&gt;We decide the quickest way to accomplish this without changing tons of code is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new &lt;code&gt;VideoAsset&lt;/code&gt; model&lt;/li&gt;
&lt;li&gt;Move existing columns over from &lt;code&gt;Presentation&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set a boolean &lt;code&gt;current_asset&lt;/code&gt; which will update when our clients select the video they want
&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Presentation&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:video_assets&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;current_video_asset&lt;/span&gt;
    &lt;span class="n"&gt;video_assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;current_asset: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Replace our dropped columns as new instance methods on Presentation&lt;/span&gt;
  &lt;span class="c1"&gt;# We've used the safety operator (&amp;amp;) on send the way we would any other method with a potential nil return value&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"playback_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"asset_id"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&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;column_suffix&lt;/span&gt;&lt;span class="o"&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="n"&gt;column_suffix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;current_video_asset&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column_suffix&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's going on here? We're dynamically defining methods on our &lt;code&gt;Presentation&lt;/code&gt; model to replace the columns we dropped and moved over to &lt;code&gt;VideoAsset&lt;/code&gt;. The existing calls in our codebase to &lt;code&gt;@presentation&lt;/code&gt; objects will now first find the current &lt;code&gt;VideoAsset&lt;/code&gt; associated with our &lt;code&gt;Presentation&lt;/code&gt; and then call the corresponding column. We don't have to go around updating controllers and views everywhere!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It's worth noting that the methods defined above could also have been accomplished using ActiveSupport's &lt;a href="https://api.rubyonrails.org/classes/Module.html#method-i-delegate"&gt;delegate&lt;/a&gt; helper. &lt;code&gt;delegate&lt;/code&gt; allows you to use this pattern more often, abstracting away the metaprogramming implementation:&lt;/em&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Presentation&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:video_assets&lt;/span&gt;
  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:playback_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:asset_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :current_video_asset&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;current_video_asset&lt;/span&gt;
    &lt;span class="n"&gt;video_assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;current_asset: &lt;/span&gt;&lt;span class="kp"&gt;true&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;h3&gt;
  
  
  Using &lt;code&gt;method_missing&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, let's look at &lt;code&gt;method_missing&lt;/code&gt;. It does pretty much what you'd expect, given its name — it allows you to account for methods that don't exist but are called on an object or class. Let's take a look at turning methods we've placed in our classes into methods that test for truthiness.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ends_with?&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;span class="n"&gt;regular_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&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;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regular_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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="nf"&gt;present?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;super&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, what's happening here?&lt;/p&gt;

&lt;p&gt;Well, we've basically recreated an &lt;code&gt;ActiveRecord&lt;/code&gt; feature for our &lt;code&gt;User&lt;/code&gt; model. Any column that exists on a table in Rails has an identically-named method ending in a &lt;code&gt;?&lt;/code&gt; added to instances of its corresponding class. We've done something similar with our &lt;code&gt;User&lt;/code&gt; class — any undefined instance method called on a user object ending in a &lt;code&gt;?&lt;/code&gt; will be tried against a method of the same name without its question mark, and turn the result into a boolean.&lt;/p&gt;

&lt;p&gt;So, for example, &lt;code&gt;@user.purchase_totals?&lt;/code&gt; will (rather than return a decimal representing the total amount of money a user has spent in our app) simply return &lt;code&gt;true&lt;/code&gt; if the number is nonzero — otherwise, &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note the use of &lt;code&gt;present?&lt;/code&gt; here. It accounts for things like empty strings and arrays, which otherwise would return true if we'd used a double-bang to assess if it was truthy or not.&lt;/p&gt;

&lt;p&gt;If there's no match, we default to calling &lt;a href="https://www.rubyguides.com/2018/09/ruby-super-keyword/"&gt;super&lt;/a&gt;, resulting in the expected behavior (a &lt;code&gt;NoMethodError&lt;/code&gt; being thrown).&lt;/p&gt;

&lt;h2&gt;
  
  
  More Advanced Metaprogramming Techniques in Ruby: An Overview
&lt;/h2&gt;

&lt;p&gt;We've covered some useful methods that can be used sparingly to help us out in everyday situations. But what about more advanced uses of metaprogramming? What else is it good for?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Domain-specific_language"&gt;DSLs&lt;/a&gt;&lt;/strong&gt;: As mentioned, ActiveRecord, the &lt;a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping"&gt;ORM&lt;/a&gt; (and DSL) that ships with Rails heavily leverages metaprogramming. That's where all those automatic methods on our objects come from. Every column we add to a table becomes two methods on our instances — &lt;code&gt;column&lt;/code&gt; and &lt;code&gt;column=&lt;/code&gt; (and &lt;code&gt;column?&lt;/code&gt; for those who were paying attention!). It isn't magic that's making this happen, it's metaprogramming — in fact, the use of the same &lt;code&gt;method_missing&lt;/code&gt; method we talked about earlier! Metaprogramming is also responsible for Rails' ability to automatically create methods like &lt;code&gt;find_by_first_name&lt;/code&gt; and &lt;code&gt;find_by(first_name: "name")&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gems&lt;/strong&gt;: If you're building a gem, chances are you'll want it to be flexible, adaptable, and to work with more than just one specific Rails stack. Most of the gems you regularly use have at least some degree of metaprogramming, and many use a lot to achieve the level of flexibility they offer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic APIs&lt;/strong&gt;: Metaprogramming can help us design dynamic APIs by allowing us to define methods on the fly based on runtime information, rather than hardcoding every possible method. For example, a RESTful API might expose resources with dynamic paths and attributes based on the data model of the underlying application (this may sound familiar — Rails' routing system does this). We can generate methods for each resource at runtime based on the database schema and dynamically respond to HTTP requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frameworks&lt;/strong&gt;: As mentioned, Rails wouldn't be Rails without metaprogramming. While it's a particularly magical framework, almost any framework you build will need plenty of metaprogramming. If you're going to build your own (presumably far more lightweight) framework, you'll need to leverage metaprogramming.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;We've covered some real ground here! We learned about dynamic method definition with &lt;code&gt;define_method&lt;/code&gt; and &lt;code&gt;method_missing&lt;/code&gt;, and about dynamic method invocation with &lt;code&gt;send&lt;/code&gt; and &lt;code&gt;public_send&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We also talked about metaprogramming's pitfalls, including its issues with readability, maintainability, and searchability. Finally, we touched on more advanced use cases like writing gems, DSLs, and frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Learning
&lt;/h2&gt;

&lt;p&gt;There are a lot of resources out there for taking Ruby metaprogramming further.&lt;/p&gt;

&lt;p&gt;RubyMonk offers a &lt;a href="https://rubymonk.com/learning/books/2-metaprogramming-in-ruby"&gt;short but free course&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A couple of other paid courses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.udemy.com/course/ruby-metaprogramming/"&gt;Ruby Metaprogramming — Complete Course&lt;/a&gt; from Udemy&lt;/li&gt;
&lt;li&gt;Chris Oliver's comprehensive &lt;a href="https://courses.gorails.com/advanced-ruby-for-rails-devs"&gt;Advanced Ruby: Behind the Magic&lt;/a&gt;. In addition to digging deep into Rails, this also has a section on metaprogramming &lt;em&gt;and&lt;/em&gt; DSLs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An oft-recommended book is &lt;a href="https://www.amazon.co.uk/Metaprogramming-Ruby-Program-Like-Facets/dp/1941222129"&gt;Metaprogramming Ruby 2: Facets of Ruby&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you found this post useful. Thanks for reading!&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>A Guide to Rails View Helpers</title>
      <dc:creator>Daniel</dc:creator>
      <pubDate>Wed, 08 Feb 2023 11:20:40 +0000</pubDate>
      <link>https://dev.to/appsignal/a-guide-to-rails-view-helpers-1d7d</link>
      <guid>https://dev.to/appsignal/a-guide-to-rails-view-helpers-1d7d</guid>
      <description>&lt;p&gt;Views in Rails don't do much besides showcasing what we want. Sure, they might render slightly different results depending on who's watching (an admin or a logged-in user has a different experience than a guest user, for example), but they don't do much processing on what they're given.&lt;/p&gt;

&lt;p&gt;Or at least they shouldn't. Often though, Rails projects wind up with a lot of logic in their views.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore how to use Rails helpers to keep our views clean and readable.&lt;/p&gt;

&lt;p&gt;But first, let's see why too much logic in our views can become problematic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Logic-heavy Rails Views
&lt;/h2&gt;

&lt;p&gt;Logic-heavy views are bad for a &lt;em&gt;lot&lt;/em&gt; of reasons. We might end up with logic-heavy views because of the pressure of a crushing deadline, or they might result from inevitable legacy code buildup. New features get introduced along with new edge cases, and it seems harmless to update &lt;code&gt;&amp;lt;%= @post.name.titlecase %&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;%= @post.name.titlecase.gsub("#", "") %&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But before you know it, this can snowball, even if you don't keep tacking on little additions to this particular line of code. If you keep doing things like this elsewhere in the view, you end up with an ugly, difficult-to-read mess.&lt;/p&gt;

&lt;p&gt;So what do we do? Should we litter our controllers with dozens of instance variables like &lt;code&gt;@normalized_titlecased_post_name = @post.name.titlecase.gsub("#", "")&lt;/code&gt;? Bloat our models with tons of methods intended only for use in our views? Probably not.&lt;/p&gt;

&lt;p&gt;Enter Rails helpers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Helpers in Rails?
&lt;/h2&gt;

&lt;p&gt;A helper is a method intended to abstract logic out of our views, leaving them more readable, less cluttered, and not directly responsible for processing what they've been passed by our controllers.&lt;/p&gt;

&lt;p&gt;Rails already ships with a lot of &lt;a href="https://guides.rubyonrails.org/action_view_helpers.html" rel="noopener noreferrer"&gt;useful helpers&lt;/a&gt; to begin with. Some you're likely already familiar with, like &lt;code&gt;form_with&lt;/code&gt;, &lt;code&gt;link_to&lt;/code&gt;, and &lt;code&gt;image_tag&lt;/code&gt;, to name a few.&lt;/p&gt;

&lt;p&gt;Most of the helpers you write yourself will naturally be much more specific to your app than what Rails includes, but you will likely still find many Rails helpers very useful.&lt;/p&gt;

&lt;p&gt;If you haven't already, familiarize yourself with the helpers Rails provides — otherwise, there's a decent chance you'll end up trying to solve a problem that's already been solved.&lt;/p&gt;

&lt;p&gt;I once wrote a method early in my Rails career to convert a number to a dollar amount, only to discover later that Rails already had a solution — &lt;code&gt;number_to_currency&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Organizing Your Helpers in Rails
&lt;/h2&gt;

&lt;p&gt;By default, a new Rails application has one helper — &lt;code&gt;ApplicationHelper&lt;/code&gt; — which all other helpers should inherit from. You've likely noticed that any time you create a controller via &lt;code&gt;rails g controller&lt;/code&gt;, Rails automatically creates a new (pluralized) helper module for you as well.&lt;/p&gt;

&lt;p&gt;While this form of organization (having a helper module for each controller) is both useful and important, it's also a bit deceptive. The methods defined in &lt;em&gt;any&lt;/em&gt; of these helpers are available in &lt;em&gt;all&lt;/em&gt; your views. So if you define &lt;code&gt;my_generic_method()&lt;/code&gt; in, say, &lt;code&gt;UsersHelper&lt;/code&gt;, and an identically-named &lt;code&gt;my_generic_method()&lt;/code&gt; in &lt;code&gt;TransactionsHelper&lt;/code&gt;, they won't only be available in their respective views — one will simply overwrite the other and almost certainly break something.&lt;/p&gt;

&lt;p&gt;So you should be fairly thoughtful when naming your helper methods. Don't assume there's an invisible &lt;code&gt;User::&lt;/code&gt; namespace or &lt;code&gt;user_&lt;/code&gt; prefix in front of the methods in your &lt;code&gt;UsersHelper&lt;/code&gt;, because there isn't, real or implied.&lt;/p&gt;

&lt;p&gt;Any helper methods generic enough to be used all over (most of these probably won't be model-specific) can go in your &lt;code&gt;ApplicationHelper&lt;/code&gt;. Methods specific to view folders (e.g., &lt;code&gt;/views/users&lt;/code&gt;) belong to helpers of the same name.&lt;/p&gt;

&lt;p&gt;Again, this is strictly for organizational purposes, given all helper methods are available to all views. But, as you might imagine, it will definitely confuse other developers (and likely your future self) if you end up finding &lt;code&gt;User&lt;/code&gt;-specific methods in your &lt;code&gt;ZooAnimalsHelper&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: Before Rails 4, this was not the case. You can &lt;a href="https://guides.rubyonrails.org/configuring.html#config-action-controller-include-all-helpers" rel="noopener noreferrer"&gt;turn this behavior off&lt;/a&gt; by setting &lt;code&gt;config.action_controller.include_all_helpers = false&lt;/code&gt; in your &lt;code&gt;application.rb&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Do's and Don'ts for Helpers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Helpers in Controllers
&lt;/h3&gt;

&lt;p&gt;You can include helpers in controllers. In modern Rails versions (5+), you can simply access them in your controllers via &lt;code&gt;helpers&lt;/code&gt; (e.g. &lt;code&gt;helpers.number_to_currency&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;It's often tempting, and you can absolutely abuse this — I won't pretend I never have. But generally, you &lt;em&gt;shouldn’t&lt;/em&gt;. It's okay to do this occasionally, and if you must, do use the &lt;code&gt;helpers&lt;/code&gt; method rather than &lt;code&gt;include&lt;/code&gt; on the entire helper module in your controller. &lt;code&gt;include&lt;/code&gt; dumps every method defined in that helper into the controller, potentially conflicting with your controller methods.&lt;/p&gt;

&lt;p&gt;If you keep finding you want to share a helper across different layers of your app, consider whether it might be better off elsewhere, like in a model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Helpers and Instance Variables
&lt;/h3&gt;

&lt;p&gt;Helpers can access any instance variables available for the view they're called in. The instance variables can be referenced inside your helpers without being passed explicitly as method arguments, but this is not advised.&lt;/p&gt;

&lt;p&gt;Instead, pass your instance variables to your helpers as regular arguments. Doing otherwise tightly couples your controllers and views (and helpers), making reuse, refactoring, testing, and debugging difficult.&lt;/p&gt;

&lt;p&gt;On &lt;em&gt;rare&lt;/em&gt; occasions, you might want to reference instance variables that aren't passed as method arguments inside your helpers. You could have a helper method that's so specific, it only belongs in one spot, or the opposite — it only leverages an instance variable you have everywhere.&lt;/p&gt;

&lt;p&gt;This is still somewhat iffy, but ultimately it's up to you. I will say though that if you're not very certain, err on the side of caution. As with anything else, you'll get a feel for things as you see what does and doesn't work over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Never Modify Objects with Helpers
&lt;/h3&gt;

&lt;p&gt;This is probably obvious, but a helper should &lt;strong&gt;&lt;em&gt;never&lt;/em&gt;&lt;/strong&gt; modify the state of a passed object or (even worse) modify that object in the database. Remember, they're responsible for helping the View display things cleanly, not changing anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Never Make Database Calls with Helpers
&lt;/h3&gt;

&lt;p&gt;Helpers should also never be responsible for making calls to the database — no part of the view should. Not only does doing so badly violate the &lt;a href="https://en.wikipedia.org/wiki/Separation_of_concerns" rel="noopener noreferrer"&gt;separation of concerns&lt;/a&gt;, it's also incredibly confusing to other developers (&lt;em&gt;where&lt;/em&gt; is this query coming from?), and can make tracking down problems difficult. I once spent hours completely baffled by where an &lt;a href="https://blog.appsignal.com/2018/04/24/active-record-performance-the-n-1-queries-antipattern.html" rel="noopener noreferrer"&gt;N+1&lt;/a&gt; was being introduced, only to discover some queries inside a loop embedded in the view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't Let Helpers Become a Dumping Ground
&lt;/h3&gt;

&lt;p&gt;One final word of caution: don't, as is so often the case, let your helpers become a dumping ground for one-off methods and little snippets of code that either serve little purpose, or might be better off as another kind of method. They should be relatively simple methods, and only concerned with the view; &lt;a href="https://en.wikipedia.org/wiki/Business_logic" rel="noopener noreferrer"&gt;business logic&lt;/a&gt; really belongs in our models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating HTML with Rails Helpers for Views: An Example
&lt;/h2&gt;

&lt;p&gt;Rails helpers can work with and generate HTML for our views. Remember Rails' built-in helpers? A &lt;a href="https://apidock.com/rails/v6.1.3.1/ActionView/Helpers/TagHelper/content_tag" rel="noopener noreferrer"&gt;content_tag&lt;/a&gt; will let us generate our own HTML.&lt;/p&gt;

&lt;p&gt;Let's say we have a zoo and want to display a list of exhibit times for a given group of animals a customer selects. We might have something like this in our view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@animals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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;animal&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"time-heading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt; (&lt;span class="nt"&gt;&amp;lt;i&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;species&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;): &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exhibit_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;in_time_zone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exhibit_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time_zone&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%m/%d/%Y %Z"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few issues with this. For one, it's a bit hard to read. We're also calling things like &lt;code&gt;in_time_zone&lt;/code&gt; and &lt;code&gt;strftime&lt;/code&gt; to format and display the exhibit time, along with a &lt;em&gt;lot&lt;/em&gt; of method chaining.&lt;/p&gt;

&lt;p&gt;It's also in no way reusable. Let's say we need to display this (or something similar) in different places on our site. If we then later decide it needs a style or content update, we have to change it everywhere. This is annoying for obvious reasons (in addition to the fact that it can be tricky to find every example if they're all slightly different).&lt;/p&gt;

&lt;p&gt;Let's see what handling this in a helper might look like:&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;# helpers/animals_helper.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;AnimalsHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;display_exhibit_times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dom_class: &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;style: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;exhibit_strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;animals&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;animal&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&amp;lt;i&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;species&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/i&amp;gt;): &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exhibit_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formatted_start_time&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="nf"&gt;html_safe&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;exhibit_strings&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;exhibit_string&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;exhibit_string&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="ss"&gt;class: &lt;/span&gt;&lt;span class="n"&gt;dom_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&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;Admittedly, this may not be the prettiest example, but it's considerably easier to read and, more importantly, removes the logic from our view entirely. It's also reusable and flexible; notice the two named arguments we've passed in — &lt;code&gt;dom_class&lt;/code&gt; and &lt;code&gt;style&lt;/code&gt;. Because &lt;code&gt;content_tag&lt;/code&gt; allows us to pass in HTML class and style information, we can pass this in any time we call this method to customize it in different places as needed.&lt;/p&gt;

&lt;p&gt;Another thing you may have noticed is the transformation of the time formatting into a call from &lt;code&gt;exhibit_time&lt;/code&gt;. This is just to point out that not all methods used in the view have to be helpers; this might be a model method, or a mixin shared across models with times and dates for easier formatting. (That isn't to say it might not also make sense to use a helper here instead; it might.)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;code&gt;.html_safe&lt;/code&gt; is needed to translate the &lt;code&gt;&amp;lt;/i&amp;gt;&lt;/code&gt; tag to HTML. &lt;code&gt;.join&lt;/code&gt; is required to return multiple content tags for use in the DOM.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our view now becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;display_exhibit_times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@animals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dom_class: &lt;/span&gt;&lt;span class="s2"&gt;"time-heading"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isn't that better?&lt;/p&gt;

&lt;h2&gt;
  
  
  A Second Example
&lt;/h2&gt;

&lt;p&gt;Let's consider another example. We need to email our customers their tickets, and we'd like to provide them with an online menu for a dining area at our zoo.&lt;/p&gt;

&lt;p&gt;In both cases, we'll probably want to render QR codes. Let's use &lt;code&gt;RQRCode&lt;/code&gt;, a gem that can do just that for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="no"&gt;RQRCode&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QRCode&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="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qr_code&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;as_svg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;color: &lt;/span&gt;&lt;span class="s2"&gt;"black"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;shape_rendering: &lt;/span&gt;&lt;span class="s2"&gt;"crispEdges"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;module_size: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;standalone: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;use_path: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't ideal for a number of reasons. For one thing, we're instantiating objects in our view. For another, we're passing every single required option, when most of them will probably be the same throughout our app.&lt;/p&gt;

&lt;p&gt;Let's move this to a helper:&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;# helpers/application_helper.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_qr_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
  &lt;span class="n"&gt;qr_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RQRCode&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QRCode&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="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;defaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;color: &lt;/span&gt;&lt;span class="s2"&gt;"black"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;shape_rendering: &lt;/span&gt;&lt;span class="s2"&gt;"crispEdges"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;module_size: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;standalone: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;use_path: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;qr_code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_svg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;html_safe&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our view becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render_qr_code&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="nf"&gt;qr_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;module_size: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much cleaner, and now we can reuse it for our restaurant!&lt;/p&gt;

&lt;h2&gt;
  
  
  One More Trick: The &lt;code&gt;tag&lt;/code&gt; Helper
&lt;/h2&gt;

&lt;p&gt;Do you ever find yourself interpolating conditional class names in your elements?&lt;/p&gt;

&lt;p&gt;Say we're listing our animal exhibits on our main page, and want to highlight certain exhibits if they're currently featured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"border-black margin-standard &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="s1"&gt;'bg-featured text-bold'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@exhibit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;featured?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@exhibit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As of Rails 6.1, you can set conditional classes &lt;a href="https://api.rubyonrails.org/v6.1.4/classes/ActionView/Helpers/TagHelper.html#method-i-tag" rel="noopener noreferrer"&gt;using the &lt;code&gt;tag&lt;/code&gt; helper&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;div&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"border-black margin-standard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bg-featured text-bold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="vi"&gt;@exhibit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;featured?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@exhibit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The classes "bg-featured" and "text-bold" will only be included if it's a featured exhibit. This can make things easier to read, especially if you've got multiple classes with multiple conditions!&lt;/p&gt;

&lt;h2&gt;
  
  
  There Isn't Always a 'Perfect' Solution
&lt;/h2&gt;

&lt;p&gt;So you've been using helpers for a while now and are comfortable with them. You've refactored your views, pulled methods out of your models and your controllers, and everything's cleaner, easier to read and test. You've noticed you're introducing fewer bugs into your application.&lt;/p&gt;

&lt;p&gt;And yet — you still have a bit of logic in some of your views, a few calls to helpers in your controllers, and a few methods that don't belong in the right place.&lt;/p&gt;

&lt;p&gt;That's completely fine. This is normal and, at least in my opinion, inevitable. Some devs claim there's always a solution — be it a helper or model method, presenter/decorator class, etc., but personally, I'm not convinced.&lt;/p&gt;

&lt;p&gt;Suppose you continually find you want to use a particular method across multiple layers of your app. In that case, it might be a good candidate for a concern, model method, or part of a separate module. But sometimes, there isn't an absolutely perfect solution for every use case or a perfect place to put code. There's certainly no such thing as a 'perfect' app. And that's okay.&lt;/p&gt;

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

&lt;p&gt;In this article, we explored how to make your Rails views cleaner and more readable using helpers. We looked at how to organize your helpers, some do's and don'ts, and how to generate HTML for your views using helpers.&lt;/p&gt;

&lt;p&gt;We finally argued that even if you master helpers, you'll likely still never achieve the 'perfect' Rails app. However, you'll certainly have an app that's easier to maintain.&lt;/p&gt;

&lt;p&gt;I hope you've found this post useful.&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" rel="noopener noreferrer"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>privacy</category>
      <category>career</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
