Skip to content

Commit

Permalink
feat: rails | AR queries | enums (TheOdinProject#29099)
Browse files Browse the repository at this point in the history
Add lesson content: section on enums

* use cases
* benefits
  * readability
  * maintainability
  * speed
  * disk space
* how to implement
  * model
  * migration
    * warn about changing persisted data after
      change to enum declaration
* comparison to scopes
* class/instance methods exposed
  • Loading branch information
sean-garwood committed Nov 23, 2024
1 parent 251389a commit 5a66c96
Showing 1 changed file with 95 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This section contains a general overview of topics that you will learn in this l
- Commonly used Rails query methods.
- N+1 queries and why they are a concern.
- What scopes are.
- What enums are.

### Relations and lazy evaluation

Expand Down Expand Up @@ -204,10 +205,92 @@ You might be thinking, Why use a scope when you can write a class method to do t
...
```

See the Additional Resources section for links to some posts that dig a bit deeper into the use cases for these two.
See the [Additional Resources section](#additional-resources) for links to some posts that dig a bit deeper into the use cases for these two.

How much do you need to understand or care about scopes? In the early going, you probably won't run into them or see why to use them. Keep them in the back of your mind for when you start working on some slightly more complicated projects that might need them.

### Enums

Enums (short for "enumerations") map symbolic names to integer constants. They
make code more readable and maintainable, and they offer a performance boost
since queries involving integers are faster than those involving strings.

Enums are perfect for representing the state of an attribute that has a [discrete
value](https://en.wikipedia.org/wiki/Continuous_or_discrete_variable). As an
example, consider a light switch: it is either in an on or an off state. It is
never between these two states, and it is never both on and off.

#### How to use enums

To implement `enum`s, we need to declare them in the model and add a column to
store them in the database table that stores instances of the model.

Enums are declared in the model's class as either an array or a hash. The hash flavor has a slight advantage
in that the integer values mapped to the `enum` are independent of their
position and the values stored in the database are explicitly stated.

```ruby
# Array declaration
class LightSwitch < ApplicationRecord
belongs_to :room
enum :status, [:off, :on] # Value depends on index of symbol
end

# Hash declaration (recommended)
class LightSwitch < ApplicationRecord
belongs_to :room
enum :status, { off: 0, on: 1 } # Value independent of position; explicit
end
```

These are functionally equivalent to the following [scopes](#scopes):

```ruby
scope :on, -> { where(status: "on") }
scope :off, -> { where(status: "off") }
```

And, just like scopes, this exposes a number of class methods that return
collections:

```ruby
LightSwitch.on
LightSwitch.not_on
LightSwitch.off
LightSwitch.not_off
```

A number of handy instance methods are also exposed:

```ruby
switchy = LightSwitch.new(status: :off)

# Predicates
switchy.on? #=> false
switchy.off? #=> true

# Getters
switchy.status #=> "off"

# Setters
switchy.status = "on" #=> "on"
switchy.off! #=> "off"
```

Since enums are stored as integers in the database and there is no native `enum` type available in most database schemas, we set their data type to `integer`:

```bash
# If building a new model, pass `column_name:integer`
# to the model generator
bin/rails g model LightSwitch status:integer

# If adding a column to an existing table:
bin/rails g migration AddToLightSwitch status:integer
```

Importantly, changing the model declaration **will not change** values that have
already been stored in the database; you must update them yourself.

### Bare-metal SQL

Sometimes, you just can't get ActiveRecord to do what you want it to. In that case, it gives you an interface to the bare metal SQL so you can just type in your query as desired. This should really be a last resort -- it's basically hard-coding your application code. Use the `#find_by_sql` method for this.
Expand All @@ -231,7 +314,14 @@ This was a lot of material, but you should have a healthy appreciation for the b

#### Advanced querying

1. Read section 14 in the [Rails Guide on Querying](https://guides.rubyonrails.org/active_record_querying.html#scopes) for a look at scopes. Again, you don't necessarily need to memorize all the details of scopes, but you should understand the concept and when it might be useful.
1. Read section 14 in the [Rails Guide on
Querying](https://guides.rubyonrails.org/active_record_querying.html#scopes)
for a look at scopes. Again, you don't necessarily need to memorize all the
details of scopes, but you should understand the concept and when it might be
useful.
1. Read [How to Use Enums in
Rails](https://blog.saeloun.com/2022/01/05/how-to-use-enums-in-rails/).
1. Read the [docs for ActiveRecord::Enum](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html).
1. Read section 19 of the same Rails guide for a look at [using SQL directly to query](http://guides.rubyonrails.org/active_record_querying.html#finding-by-sql).

</div>
Expand All @@ -247,6 +337,7 @@ The following questions are an opportunity to reflect on key topics in this less
- [What is an example of an N+1 query?](#n1-queries-and-eager-loading)
- [What method is used to deal with an N+1 query?](#n1-queries-and-eager-loading)
- [When would you use a class method in place of a scope?](#scopes)
- [When should you consider using enums?](#why-use-enums)

### Additional resources

Expand All @@ -260,3 +351,5 @@ This section contains helpful links to related content. It isn't required, so co
- [N+1 Problem: Optimized Counts with Joins and Custom Select](https://www.youtube.com/watch?v=rJg3I-leoo4)
- [Speed up ActiveRecord with a little tweaking](https://blog.codeship.com/speed-up-activerecord/)
- [A useful gem that identifies N+1 queries](https://github.com/flyerhzm/bullet)
- [ActiveRecord::Enum API
docs](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html)

0 comments on commit 5a66c96

Please sign in to comment.