## An Example of an ERB Component

In a previous post I discussed my need for a flexible function for generating values to pass into the HTML attribute options hash of the content_tag helper. In this post, I want to discuss one particular context in which I needed the meld method.

For one particular work project I found myself building a few “ERB components”, that is, ERB partials that had some flexibility around their HTML output via params passed on render. In one specific example, I was building a simple partial for rendering a key-value pair. I was using the description list HTML element (dl), but I wanted the flexibility to have the entry render either as a “column” or as a “row”, e.g.:

#### Column Entry

 1 2  Key value 

#### Row Entry

 1 Key value 

So, I wanted one ERB partial where I could get either output based on params passed to the render call of that partial.

Using Bootstrap 4, I know how I wanted the HTML for each entry to look:

#### Column Entry

 1 2 3 4 
Key
value


#### Row Entry

 1 2 3 4 
Key
value


So, I could write some aspirational ERB for how I would like the partial to work:

 1 2 3 4 5 6 7 8 9 <%= content_tag(:dl, **props_for(:entry)) do %> <%= content_tag(:dt, **props_for(:key)) do %> <%= value_for(:key) %> <% end %> <%= content_tag(:dd, **props_for(:value)) do %> <%= value_for(:value) %> <% end %> <% end %> 

I then could also write some aspirational render calls:

#### Column Entry

 1 2 3 4 5 6 7 <%= render('entry', key: 'Key', value: 'value', props: { entry: { class: %[text-center my-0] }, key: {}, value: {}, }) %> 

#### Row Entry

 1 2 3 4 5 6 7 <%= render('entry', key: 'Key', value: 'value', props: { entry: { class: %[d-flex my-0] }, key: { class: %[col-4] }, value: { class: %[col] }, }) %> 

Now, I just needed to write the props_for and value_for methods for the partial. The first thing I need is to access the params passed into the partial. With ERB partials, you can get the full set of params passed into a partial via the local_assigns variable. local_assigns references a hash of the params. So, I wrote my methods like so:

 1 2 3 4 5 6 7 8 9 10 <% def props_for(key) local_assigns.dig(:props, key) end %> <% def value_for(*keys) keys.reduce(local_assigns) { |hash, key| hash.try(:dig, key) } end %> 

NOTE: The value_for method here is precisely the same as the access method I discussed in this past article.

While simple and elegant, these methods have two problems. First, local_assigns is not accessible from any scope except the outer partial scope; you will get a undefined local variable or method 'local_assigns' error when you try to run these methods in the partial. Second, these methods won’t handle params passed using string keys. Let’s refactor and fix both of these issues:

 1 2 3 4 5 6 7 8 9 10 11 <% instructions = local_assigns.deep_symbolize_keys || {} %> <% def props_for(k, instructions) instructions.dig(:props, k.to_sym) end %> <% def value_for(*keys, instructions) keys.map(&:to_sym).reduce(instructions) { |hash, key| hash.try(:dig, key) } end %> 

Now, we can simply pass the symbolized hash of local_assigns into the methods as a param, and we ensure that we are always working with symbols. Our final ERB partial-as-component looks like so:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <% instructions = local_assigns.deep_symbolize_keys || {} %> <% def props_for(k, instructions) instructions.dig(:props, k.to_sym) end %> <% def value_for(*keys, instructions) keys.map(&:to_sym).reduce(instructions) { |hash, key| hash.try(:dig, key) } end %> <%= content_tag(:dl, **props_for(:entry, instructions)) do %> <%= content_tag(:dt, **props_for(:key, instructions)) do %> <%= value_for(:key, instructions) %> <% end %> <%= content_tag(:dd, **props_for(:value, instructions)) do %> <%= value_for(:value, instructions) %> <% end %> <% end %> 

This will output the HTML we desire given the render calls outlined above.

#### Column Entry

 1 2 3 4 5 6 7 <%= render('entry', key: 'Key', value: 'value', props: { entry: { class: %[text-center my-0] }, key: {}, value: {}, }) %> 

outputs

 1 2 3 4 
Key
value


#### Row Entry

 1 2 3 4 5 6 7 <%= render('entry', key: 'Key', value: 'value', props: { entry: { class: %[d-flex my-0] }, key: { class: %[col-4] }, value: { class: %[col] }, }) %> 

outputs

 1 2 3 4 
Key
value