TIL You Can Add Virtual Attributes to Rails Models
Tags: todayilearned, ruby, rails
Published: 2018-02-22 18:59:54

---
title: TIL You Can Add Virtual Attributes to Rails Models
published: true
description: Models are actually classes, what?! A lesson learned.
tags: todayilearned, ruby, rails
---

_Disclaimer: I learned about it yesterday. Woops._

I was building out an email sending feature, and the basic idea was to assign a role to a user that has an expiration date, and also allow the option to send an email letting the user know. The end result is this:

![Example image of role adding form](https://thepracticaldev.s3.amazonaws.com/i/q0rnn62h844pbshymnjc.png)

Building out the form was pretty straightforward and easy, especially since most of the work was done for me already with the [Administrate gem](https://github.com/thoughtbot/administrate). There was even a nice wiki that told me how to make a custom form field, and it even includes a command line generator! Great! This is all so easy! Well, like most problems, this one was not as simple as I thought.

The generator creates a view partial, and after a bit of configuration, all I had to do was add a few more fields:
```erb
<div class="field-unit__label">
  <%= f.label field.attribute %> #=> Give superpowers?
</div>
<div class="field-unit__field" style="width: 2em;">
  <%= f.check_box field.attribute %> #=> User.add_role superpowered
</div>
<%= f.date_field "superpower_expiration", 
    value: (Time.now + 1.year).strftime("%Y-%m-%d"), style: "width: 25%;" %>
  <div style="margin-left: 1em;">
    <%= f.label "Send email telling them they have it?" %>
  </div>
  <div style="margin-left: 1em;">
    <%= check_box_tag "superpower_email" %>
  </div>
```
<figcaption>View partial or partial view? You decide.</figcaption>

Okay, that's all I have to do right? The form submitted, everything works, bada bing bada boom _push that code to production_. Well, once the code gets to production, I realize it doesn't work -- actually, Jess realizes it doesn't work, and let's me know I goofed. Lesson learned. Always check your code, and see...
{% devcomment 2bmd %}

So, back to the code. Here's what I thought was the offending code:

```ruby
# Controller
  class UsersController < ApplicationController
    def update
      user = User.find(params[:id])
      UserRoleService.new(user).check_for_roles(params[:user])
      if user.errors.messages.blank? && user.update(user_params)
        # happy path
      else
        # sad path
      end
    end

   private

   def strong_params
     accessible = [user, model, attributes]
     params.require(:user).permit(accessible)
   end
  end

# user_role_service.rb, the service that handles new roles
  class UserRoleService
    def check_for_roles
      # some other logic
      NotifyMailer.delay.superpowers_awarded_email(@user) if params[:superpower_email] == "1"
    end
  end
```

Ahh, I forgot that `params[:superpower_email]` is a normal parameter and not permitted. I tried to `push` it into the `accessible` array, but the `:superpower_email param` was still nowhere to be found. Well, since it didn't work, I had to change the checkbox field:

```erb
  <div style="margin-left: 1em;">
    <%= f.check_box "superpower_email" %>
  </div>
```

But that threw a `NoMethodError, undefined method for User`. Well, the `superpower_email` wasn't supposed to be an actual column in the model, and that would be ridiculous if I had to create a migration and make that column. There had to be a better way. I asked for some help, and Ben said:

![Ben's response: virtual attributes](https://thepracticaldev.s3.amazonaws.com/i/umxi04o641o2u52wkxdb.png)
<figcaption>I replied with :explodingheademoji:, and yeah, it's a real emoji.</figcaption>

So now that you've read so much of this story, the question finally arises: what is a virtual attribute? Well, after some Googling, I figured it out.

Rails models are classes that inherited from `ApplicationRecord`, and just like classes we can add attributes to them. Almost always, model attributes link with a database column, since the model is essentially the table you're interacting with. There are some cases though where you wouldn't need to link an attribute to the database, and  **therefore you can create the attribute just like you would with any Ruby class. This was the big "Aha!" moment for me.**

Now, I could have went with the simplest way of creating the class attribute:
```ruby
class User < ApplicationRecord

  def superpower_email; end

end
```

But that didn't seem quite right. After all, I didn't really need a method to run any logic. So, I turned to this solution instead:

```ruby
class User < ApplicationRecord
  attr_acccessor :superpower_email
end
```

The reason I needed it to be an `accessor` attribute as opposed to _only_ a reader or _only_ a writer was so that the form could both render the attribute properly as well as pass along the data as a parameter. Also, this would make it easier to add future extraneous attributes later on if I ever needed them.

_Update: As @kinduff mentioned in the comments, it might be misleading about the difference between `accessor` and `reader` + `writer`. `accessor` is synonymous and an alias of using both `attr_reader` and `attr_writer`._

So I go ahead and add the attribute into the User model _(class)_, add the new attribute as an allowed strong parameter, check that an email is queued as a job, and it works! Huzzah! Back into production the code goes!

### Thanks for reading this long TIL. :)

### Reference:
1. [Go Rails Episode and Transcript - Virtual Attributes and Rails 5 Attributes API](https://gorails.com/episodes/virtual-attributes-and-rails-5-attribute-api)