Using each_slice in Rails
Tags: rails, ruby, webdev, tutorial
Published: 2021-10-23 15:48:53

I wanted to take a moment to share about an approach to a problem I encounter with our application at work. I was working on a multi-step controller pattern which returned data from an external API to move through a registration process. The data did not persist into a database, but instead, was presented from JSON.

The design called for a grid of 12 cards that could be paginated, searched, and selected. For instance if we have 32 cards, on multiple pages, we needed to be able to select all the cards, and on submit, persist 32 cards to the next controller action with an array of data. After reviewing the specification, we decided to use [DataTables](https://www.datatables.net/), which we have already used throughout this Rails 5 application.

**The problem**: How do you populate a card view into a HTML table? DataTables will read a table, add pagination and search automatically but does not work with a CSS Grid Card view.  

**TLTR**: Just go grab the [source code](https://github.com/eclectic-coding/article_rails_each_slice) if you prefer. 

## Base application
So, I have created a base Rails 6 application to start with set up with Webpacker, Bootstrap5, and I have added jQuery for DataTables. I have created a single resource called Player, with the attribute of `name`, used faker to populate the database with thirty instances, and displayed in a card view on the index action. 

![Players card view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmxmmxgvcfa1scodea0d.png)

## Set up Datatables
We are going to set up DataTables just so we can see the results. You can go to the DataTables [Download](https://datatables.net/download/) page to confirm which package you will need. In my case, I am using the package which uses Bootstrap5. 

`yarn add datatables.net-bs5`

Next set up a file to configure. I just placed in my packs directory: `app/javascript/packs/player-datatables.js`, and do not forget to import from `application.js`: `import "./player-datatables.js"`.

To set up, call like so in `player-datatables.js`:
```js
require('datatables.net-bs5');

$(document).ready( function () {
    $('#players').DataTable(); // players ID for our table
} );
```

However, I want to make a few configuration changes. 
- Set the pagination parameter of 4 rows
- Do not show the rows filter select and label
- Hide the search box label
- Add a placeholder into the search box
- Inject some CSS classes for the search box styling

```js
require('datatables.net-bs5');

$(document).ready( function () {
    $('#players').DataTable({ 
        "pageLength": 4,        // set rows for pagination
        "bInfo": false,         // Hide show columns select
        "bLengthChange": false,  // Hide bInfo 1 of n shown
        "oLanguage": {
            "sSearch": "",
            "sSearchPlaceholder": "Search players..."
        }
    });
    // Add classes to search box
    $('#players_filter').addClass('d-flex justify-content-end me-3') 
} );
```
DataTables will look for a table with the ID of `players`, use the table rows to populate the data, and do its magic. The table **must** have column headings for each column, but we can kind of fake it like I have done here:

```html
<table id="players">
  <thead class="d-none">
    <tr>
      <td>Player</td>
      <td>Player</td>
      <td>Player</td>
    </tr>
  </thead>
<tbody></tbody>
</table>
```
Now we have an empty table populating on the index view. 
![Empty Data Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivlz5vcu6jttxmi7yw5s.png)

## Logic of iteration
Let's look at the logic of iterating over a collection to populate a view. Iterating normally would look something like this using `each`:
```erb
  <tbody>
    <% @players.each do |player| %>
      <tr>
        <td><%= player.title %></td>
      </tr>
    <% end %>
  </tbody>
</table>
```
However, we are trying to replicate a card grid into a table, which means that we want to force only three columns, then move to the next row, using the same data in each table cell. The above loop will place our entire collection into one column. 

There might be other solutions, maybe using `each_with_index` and evaluating the index with `modulus`, although this did not work for me. 

First let me say, I love Ruby, which had the perfect method for this use case: `each_slice`, which will iterate the given block for each slice of a number of specified elements. If no block is given, it will return an enumerator.

Notice this example:
```shell
irb(main):009:0> (1..10).each_slice(3) { |a| p a }
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]
=> nil
```
`each_slice` return arrays with the number of elements specified (3), and then an array of the remainder elements. 

## Solution
So, to start building our view with `each_slice`, the `<tbody>` section will start as so:

```erb
<tbody>
    <% @players.each_slice(3) do |player| %>
      <tr role="row">
        ...
      </tr>
    <% end %>
  </tbody>
```
Now, remember, `each_slice` returns an array, so `player` is an array, not an instance, in which will will need to iterate:
```erb
<tbody>
    <% @players.each_slice(3) do |player| %>
      <tr role="row">
        <% player.each do |p| %>
          <%= render partial: "players/player", locals: { p: p } %>
        <% end %>
      </tr>
    <% end %>
  </tbody>
```
This seems like we are finished, but remember if we do have a remainder array returned from `each_slice`, we have not addressed these. In fact, DataTable will crash. See console output:
![Data Tables console errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0r8ul48ymiix2xz7fa76.png)
**The reason**: DataTables expect correctly formatted table markup. The browser is more forgiving, and will display the table. However, all the DataTables features will not be present (i.e. search, pagination). If there are remainders, there is no `<td></td>` tags for those cells. Luckily, Ruby can help us real easily to check if there are any remainder in `player`:
```erb
<tbody>
    <% @players.each_slice(3) do |player| %>
      <tr role="row">
        <% player.each do |p| %>
          <%= render partial: "players/player", locals: { p: p } %>
        <% end %>
        <% (3 - player.length).times do %> // calc remainders
          <td></td>
        <% end %>
      </tr>
    <% end %>
  </tbody>
```
## Conclusion
So, what have we learned? First, Ruby is beautiful, but beyond the obvious, we have learned about `each_slice`. This is a method you may not use everyday, but with this example you have seen a least one use case. Be sure to leave a comment or hit me up on [Twitter](https://twitter.com/EclecticCoding), and I hope you have enjoyed.