# Extending the Options Class in Ruby on Rails

In the last post about [options and configurations](/content/blog/ruby-on-rails-options-configurations/index.html) in Rails, we discussed how to use options and configurations to give your code more depth in the design. This is an extension of that, quite literally. We’ll be reviewing how to extend the options class a bit more.

So the goal of the options class was to take _all_ the options and turn them into something useful. We essentially want a user to pass in as little information as needed to generate the correct markup, but we want to set some useful defaults. But those defaults need to be something that can be overridden.

## A Ruby on Rails Helper Example

Our old helper `alert` can really help here.

```rb
def alert_message(text = '', level: 'warning', header: nil, icon: true, dismissible: true, &block)
  alert = BootstrapHelpers::Alert.new(self)
  icon = alert.generate_icon(icon, level)
  icon = nil unless block.nil?
  content = capture(alert, &block) unless block.nil?
  bs_render template: 'alert/alert', content: content, locals: { icon: icon, dismissible: dismissible, level: level, header: header, content: content, text: text }
end
```

This method isn’t too bad, but we have a lot of logic in the method declaration it’s self. We also don’t allow any arbitrary options either. Let’s clean that up a bit.

```rb
def alert_message(text = '', options = {}, &block)
  options = BootstrapHelpers::Options.new(options)
  level = options.extract(:level, 'warning')
  header = options.extract(:header)
  icon = options.extract(:icon, true)
  dismissible = options.extract(:dismissible, true)
  alert = BootstrapHelpers::Alert.new(self)
  icon = alert.generate_icon(icon, level)
  icon = nil unless block.nil?
  content = capture(alert, &block) unless block.nil?
  bs_render template: 'alert/alert', content: content, locals: { options: options, icon: icon, dismissible: dismissible, level: level, header: header, content: content, text: text }
end
```

There now the method declaration is cleaner, of course the method is much more verbose, and were passing a ton of things into the template. We should fix that too.

```rb
def alert_message(text = '', options = {}, &block)
  unless block.nil?
    options.merge! text if text.is_a? Hash
    options[:icon] = false
    text = nil
  end
  alert = BootstrapHelpers::Alert.new(self)
  bs_render template: 'alert/alert', klass: alert, block: block, options: options, locals: { text: text }
end
```

This makes for a much cleaner method. For now, the unless the block is used to help set different defaults if a block is provided. Essentially if your using a block then the default icon markup is not likely to work so that we will leave that to your block.

```erb
<%= alert_message 'You should really read this alert.' %>
```

The important part is that we can call a message like this (or much more complicated examples in the todo application repo) and get markup like this:

```html
<div style="" class="alert alert-warning fade show alert-dismissible " id="1568c8b6-bb81-44e4-a919-19c3a01b20e8" role="alert">
  <p>
    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
      <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"></path>
    </svg>
    You should really read this alert.
  </p>
  <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
```

This reduction in complexity is what were after. All without having to create a totally custom options parser. It just takes the right options and works.

## Modifications to the Options Class

We had to make a few modifications to the options class. Nothing major but let’s go over them.

```rb
def extract(key, default: nil)
  return instance_variable_get("@#{key}") unless instance_variable_get("@#{key}").nil?
  return default unless @named_args.key? key

value = @named_args.delete key
  instance_variable_set("@#{key}", value)
  return default if value.nil?

value
end
```

What is interesting about this class is the fact that we set an instance variable then delete the key/value pair from the @named\_args hash. Because named\_args uses the double spat (\*\*), _everything_ gets stuffed into that hash. We may not want those options passed along to the parser and turned into css classes.

For some bonus points, why are these two lines not the same?

```rb
return instance_variable_get("@#{key}") unless instance_variable_get("@#{key}").nil?
return instance_variable_get("@#{key}") unless instance_variable_get("@#{key}")
```

The second line will create a subtle bug, and is a good example of the _dangers_ of a guard clause used incorrectly.

```rb
def to_s(include_classes: [])
  process unless @processed
  include_classes = include_classes.join(' ') if include_classes.is_a? Array
  "style=\"#{styles.join(';')}\" class=\"#{include_classes} #{classes.join(' ')}\" id=\"#{id}\"".html_safe
end

def process
  @processed = true
  parse_out_classes(@named_args)
end
```

These two together make a kind of semaphore. Essentially making sure that
`parse_out_classes` is only called one time. That essentially makes `to_s`
the end of the call chain. This has the effect of removing some of the processing from the `parse_out_classes`. It’s not perfect, but it helps reduce a ton of double processing. Anything that might be left over can be excluded from the `BootstrapHelpers.configuration.excluded_attributes` configuration directive if need be. For example:

```rb
options = BootstrapHelpers::Options.new({class: 'dog cat mouse animal', size: 12, lg: {display: false}, sm: {display: true}, icon: true, small: 'big', food: 'pizza'})
options.to_s

# => style=\"\" class=\" col-12 d-lg-none d-sm-block small-big food-pizza dog cat mouse animal\" id=\"635fd820-7c8f-4708-befa-5bf41555359b\"\n
# notice the food and small css classes that came out. This is useful but not
# in this case, and it may cause a problem for css classes to get assigned like
# that

options = BootstrapHelpers::Options.new({class: 'dog cat mouse animal', size: 12, lg: {display: false}, sm: {display: true}, icon: true, small: 'big', food: 'pizza'})
food = options.extract(:food, default: 'pie')
small = options.extract(:small, default: false)
options.to_s

# => style=\"\" class=\" col-12 d-lg-none d-sm-block dog cat mouse animal\" id=\"ec6cc076-7cd8-4f34-a8b5-2e56c0b8ef99\"\n
# because we used these options (food and small) we know not to pass them along
# as classes.
```

There are other ways to accomplish the same thing, but this keeps us from
processing many times for the same thing. Remember, most of the calls will use the options class, so saving time like this is easy and seems useful. We don’t want to over-optimize though. This is a good compromise.

## Ideas for Next Time

The only thing I don’t like that much is the semaphore. While it doesn’t exactly make a _secret handshake_ of calls, it does change the output depending on the order in which you call `to_s` and `extract`. That said the only way to ‘fix’ it is either whitelist keys, or make the `Options` class aware of what attributes its caller may think are protected.

The first is not too bad, but the second totally defeats the purpose of the class. I will have to decide rather to white list, live with it, or come up with another way to tell the `Options` class that some named arguments don’t belong in the to_s method.
