We learned in previous chapters that Active Record gives us the tools we need to perform a variety of activities on our models. For example, we added validations to our User model to make sure email addresses are unique and valid. We also added a callback to our Comment model to email the article’s author anytime a comment is created. We were able to pass instances of these models to the form_with helper in their respective form partials to get default values and error messages with minimal effort. Powerful stuff!
But at the end of the previous chapter, we realized that our “Email a Friend” form is lacking these features. Currently, if one were to fill out that form without populating any values or by supplying an invalid email address, our blog application would happily accept those invalid values and even claim to have successfully sent the email!
We could create a new Active Record model to represent these “Email a Friend” submissions to give us validations, callbacks, and other Active Record goodies, but that would require us to create a database table to store these submissions. That’s not necessarily wrong, but sometimes we want these benefits of Active Record without needing the database-related functionality. (In our example, we don’t have a desire to store these “Email a Friend” submissions—just to validate them.)
We could also reinvent the wheel and make our own validation functions, our own callback mechanisms, and other features we need. But that’s time-consuming and error-prone.
What if we could have the parts of Active Record we need, without the parts we don’t need? This is precisely where Active Model comes in; in fact, you might be surprised to learn that Active Record validations, callbacks, and many other features are actually supplied by Active Model!
In this chapter, we’ll learn how to mix in some of the most commonly used Active Model modules into POROs (Plain Old Ruby Objects) to gain some of the best features of Active Record in models which don’t need database storage.
After a brief tour of some of the most commonly used Active Model modules, we’ll improve our blog application by using Active Model to improve how we handle “Email a Friend” submissions.
A Tour of Active Model
Like many well-designed libraries, Active Model is composed of several modules, each focused on a specific set of behaviors. This type of organization allows the developer to choose which parts of Active Model they need, rather than being forced to include the whole set of behaviors.
In this section, we’ll explore some of the most commonly used modules in Active Model and learn how they can enhance our POROs. For illustration, let’s build a Car class, which will have nothing to do with our blog application. After we’ve toured some of the most commonly used modules of Active Model, we’ll leave this Car class behind and go to work improving our blog application.
Basic Car Class to Help Illustrate Active Model Moduleshttps://gist.github.com/nicedawg/a1ba973abc0a29df829ef46ab78b20de
We tried to instantiate a new car by supplying its attributes in the constructor—the new method. That works with Active Record models, but not this Car class. So we instantiated a new car with no arguments and assigned each attribute a value one by one. That worked as expected, but not being able to supply a hash of attributes and their values to the constructor will be inconvenient. Hopefully, we can fix that.
ActiveModel::Attributes
Including ActiveModel::AttributeAssignment in the Car Classhttps://gist.github.com/nicedawg/96ffbaed32abc8ef78acc90149345343
In the preceding listing, we included the ActiveModel::AttributeAssignment module in our Car class. Generally, that means that our Car class gains new methods from the module we included. One such method we gained is assign_attributes—a method that takes a hash of key-value pairs and uses the corresponding setter for each key to assign the value from the hash.
Another thing that warrants explanation is our initialize method. Every Ruby object has an initialize method, usually supplied by a parent class. Whenever the new message is sent to a class, Ruby creates a new object from the class and then calls the initialize method on that new object. We wanted to be able to assign a hash of attributes when we call Car.new, so this is the right place to do it! Our initialize method takes an argument (with the default being an empty hash) and then calls the assign_attributes method on our car object with that hash, if it exists.
Finally, we call super(), to make sure that any initialize methods on our parent classes are called as well. The parentheses after the call to super might strike you as strange—it’s not typical Ruby code style to affix empty parentheses to method calls. However, super is a little special; with no parentheses, its default behavior is to send the arguments of the method it’s in to its parents also. That’s often helpful, but in our case, it would cause an error, as our parent class does not expect any arguments.
That was a little tedious. For a feature we might like to use frequently—the ability to assign attributes via our model’s constructor—this feels a little like the tedious boilerplate code which we’ve come to expect that Ruby on Rails can help us avoid. Don’t worry; later in this chapter, we’ll learn how to avoid needing to take these steps.
We’re making progress! Slowly, but surely, our Car model is becoming a little easier to work with—thanks to ActiveModel::AttributeMethods.
ActiveModel::Callbacks
Extending Our Car Class with ActiveModel::Callbackshttps://gist.github.com/nicedawg/0bcaa23b2450a9d81859ed28d7089719
First, we extended our class with the ActiveModel::Callbacks module. Why extend rather than include? The main reason is that ActiveModel::Callbacks adds class methods to our class, while ActiveModel::AttributeAssignment added instance methods. If in doubt, consult the source code of the module you wish to use in your class, or use your favorite search engine to find example usage.
Next, we used the class method define_model_callbacks to register a new lifecycle event—namely, :paint—for which we might want to run code before, after, or around that event.
Then, we configured our class to run keep_it_waxed before the car is painted and to run notify_dmv after the car is painted. (We added those methods as private methods, which simply log some output.)
Finally, we modified our paint method to call run_callbacks :paint. (The fact that we named our callback event :paint and the fact it happens in a method called paint are just a coincidence. The callback name does not need to match any method names.) By wrapping our assignment of color in the run_callbacks block, this provides enough information for our code to know when to run any applicable callback methods. We also added a logging statement inside the callback to indicate when the color is actually being changed.
Alright! We see that our registered callback methods were performed in the right order. Once again, Active Model has added some powerful behavior to our Car class with not too much effort. However, we just realized we have a slight flaw in our logic surrounding notifying the DMV about color changes. Often, someone might paint their car the same color to repair paint damage, but to keep the same look. In that case, there’s no need to notify the DMV. We only need to notify them if the color actually changed.
ActiveModel::Dirty
We need to modify our Car class to only notify the DMV if the paint color actually changed—not when the car was repainted the same color.
If we were notifying the DMV in our paint method, we could simply compare the requested color to the current color. But we chose to notify the DMV in a before_paint callback and no longer have access to the requested color.
The ActiveModel::Dirty can help us here. Active Record uses this module to keep track of which attributes have changed. By including ActiveModel::Dirty in our Car class, we can achieve the same functionality, albeit with a little more work.
You might wonder, why is this module called Dirty? Often, in programming, an entity is called “dirty” if it has been modified, but the new values have not been saved or finalized. Since this module helps us keep track of which attributes have been changed (but not finalized), Dirty is an appropriate name.
Including ActiveModel::Dirty in Our Car Classhttps://gist.github.com/nicedawg/97a87d1f4bd19f7577bd500fbe51632a
First, we included the ActiveModel::Dirty module to add methods to our class which can help keep track of which attributes’ values have changed. But unlike with Active Record, we don’t get this behavior on our attributes automatically—we must define which attributes will be tracked and must manually set when an attribute’s value is changing and when we should consider the change to be complete.
To declare that we want to track the “dirty” status of the color attribute, we add the define_attribute_methods :color line to our class. If we had more attributes we wanted to track, we could add them to this same line.
Next, we added a condition to our notify_dmv callback, so that we only perform notify_dmv if the color actually changed.
However, since this model is not an Active Record model, it’s up to us to keep track of when the color attribute has changed and when we should consider the changes to be applied. So we call the color_will_change! method in our paint method only if the new color does not match the current color. By calling color_will_change!, the color attribute is now considered “dirty,” and color_changed? will return true.
Finally, in our notify_dmv method , after having warned the user, we call changes_applied to clear the “dirty” status from our attributes. If we had not done this, any subsequent calls to color_changed? would return true—even if we’re repainting the car with the same color!
After reloading the console and re-instantiating our Car object with the color green, we painted it black and saw the warning to notify the DMV, as we expected. Next, we repainted the car black. As we hoped, we did not see the DMV warning. Finally, to be certain, we painted the car red and saw the DMV warning again.
Our Car class is certainly becoming more featureful. Another wishlist item of ours is validation. Will we be able to add Active Record–style validations to our Car class?
ActiveModel::Validations
As we’ve seen in previous chapters, being able to validate our models is essential. Active Record makes it easy and elegant to validate models—but our Car class is not an Active Record model. Are we doomed to reinvent the wheel?
Including ActiveModel::Validations in Our Car Classhttps://gist.github.com/nicedawg/a68604460656afe78f6a0739477d7918
First, we included ActiveModel::Validations into our class. If you look closely, we also removed ActiveModel::Callbacks. Why? It turns out that ActiveModel::Validations already includes ActiveModel::Callbacks, so we don’t need to include it separately.
Next, we added validations to ensure that all of our attributes are present and also to ensure that the year is reasonable.
There we have it! Simply by including ActiveModel::Validations, we gained the ability to define validation rules, to check an object’s validity, and to see the validation errors—just like we’ve done in previous chapters with Active Record.
There are more Active Model modules we could explore, but we’ve covered some of the most commonly used modules. However, we’re not quite ready to apply our knowledge to our blog application; there’s one more Active Model module to cover.
ActiveModel::Model
So far, we’ve added attribute assignment, callbacks, dirty tracking, and validation to our Car class by adding various Active Model modules to our class and making relevant code changes.
Though we’ve made significant enhancements to our Car class with not that much code, it is beginning to feel a little heavy. We miss the elegance of Active Record classes which give us so much functionality for free.
There’s a bit of bad news too: our Car class is not ready to be used in our Rails app the same way we can use Active Record objects throughout the app. Rails favors convention over configuration, and Active Record objects follow suit. We can pass an instance of an Active Record object to a path helper, to a form_with helper, to a render call… and Rails does the right thing. Unfortunately, as our Car class currently stands, using instances of the Car class in Action Pack and Action View will require more configuration than convention.
Yes, there are some more Active Model modules we could include to change our Car class to play more nicely with Action Pack and Action View, but we’re already starting to feel that our Car class is getting a little too complicated.
Thankfully, we have ActiveModel::Model to rescue us. The Model module from Active Model is a bit of a super-module. ActiveModel::Model itself includes the AttributeAssignment and Validations modules, as well as a few more (Conversion, Naming, Translation) which will help our Car model play nicely with Action Pack and Action View. It also implements the behavior which we added manually in our initialize method.
In other words, simply by including this module and then removing some code, we’ll have more functionality than when we started!
Including ActiveModel::Model in Our Car Classhttps://gist.github.com/nicedawg/f38da1df450cc5860eebb420cc47220a
As you can see, we included ActiveModel::Model and removed the modules we no longer need to manually include. We also removed our initialize method as ActiveModel::Model gives us the ability to assign attributes in our class’s constructor.
If you’d like to, reload your rails console and try out the features again—assigning attributes in the constructor, the callbacks, tracking change status, and validations. It all still works, with a little less code!
But the additional modules which ActiveModel::Model has included have given us some new functionality, so that instances of our Car class will work smoothly with Action Pack and Action View.
Now that we’ve explored various Active Model modules and learned how to enhance our simple Car class to behave more like an Active Record module, we’re ready to apply what we’ve learned to our blog.
Enhancing Our Blog with Active Model
You might remember that the issue which spawned this chapter (besides the fact that it’s valuable to know about Active Model) is that our “Email a Friend” form is not up to our standards; it doesn’t validate the input and claims to have “successfully sent the message” even if the user left all the fields blank or entered a syntactically invalid email address in the form!
We weren’t sure how to handle this, because we didn’t really want to create an Active Record model for “Email a Friend” submissions. At the moment, we have no need to store or retrieve these submissions. And we didn’t want to reinvent the wheel or bloat our controller by adding validation code and error messaging in an unconventional way.
But now that we know how to create a model that mostly behaves like the Active Record models we’re used to working with, we’re ready to fix this.
Create an EmailAFriend Model
EmailAFriend Model in app/models/email_a_friend.rbhttps://gist.github.com/nicedawg/45fd55a2f9527675b18c8505352e8463
This model is simple enough. We included ActiveModel::Model to gain validations and other methods which will let the EmailAFriend model work well with Action Pack and Action View. The validator which checks the format of the email address uses a regular expression which is provided by the URI module—a part of any standard Ruby installation. (Note: This validator only checks that the email address is syntactically valid—that is to say, that it adheres to the rules for the format of an email address. It does not ensure that the domain is valid or that it accepts email or that a mailbox exists for that user.)
Now that our model can validate that a name and properly formatted email address were supplied, we can rework our controller and views to make use of our new model.
Update Controller/Views to Use Our New Model
Provide a New EmailAFriend Object to ArticlesController#showhttps://gist.github.com/nicedawg/69a3789f84e91a63e68345ab7d4be814
Use @email_a_friend in app/views/articles/_notify_friend.html.erbhttps://gist.github.com/nicedawg/b985133d8c917cb3367953ba4dc6ee0c
Nothing surprising there. As we’ve done before, we simply added model: @email_a_friend to tell the form_with helper that we wanted our form to be based on the object we passed. We also told the form_with helper we wanted the resulting form to have the id “email_a_friend,” which will be handy in a minute. Finally, we also added a snippet of code similar to what we’ve used elsewhere in order to display any error messages in the form.
If you were to try out the Email a Friend form now, you would see that nothing has really changed yet. That’s to be expected, as we haven’t yet added the code to make sure the Email a Friend submission was valid.
Validating Email a Friend in ArticlesController#notify_friendhttps://gist.github.com/nicedawg/5dc3292f1944af645df85afd9c627753
First, note the new private method we added at the bottom—email_a_friend_params. It’s always good practice to make sure that we whitelist the params we expect to receive. This has the effect of saying “the only params we’ll accept for an EmailAFriend model are name and email.”
Now take a close look at the changes we made to the notify_friend action. We instantiate a new EmailAFriend object using the params submitted from our form. If the object is valid, we do what we’ve always done—we schedule the email to be delivered, and we redirect with a success notice. However, instead of just grabbing the name and email straight from the params hash, we now get them from our instance of the EmailAFriend class.
Finally, we added an else clause. If the submission was not valid, we need to show the form again with the relevant error message. We also return a status of :unprocessable_entity, which translates to HTTP status code 422.
Adding app/views/articles/notify_friend.js.erbhttps://gist.github.com/nicedawg/b4bd7f04722ca6ad14e518bb0829b539
When this JS template is rendered, it instructs the browser to find the element with the id “email_a_friend”—which is our “Email a Friend” form—and replaces its content with the output of the notify_friend partial which we’re already using. When this template is rendered as the result of the submission being invalid, it will include the error messages that explain why the form couldn’t be submitted.
Try It Out
Now everything’s in place. Try it out! Try submitting a blank “Email a Friend” form; you should see error messages about Name and Email being blank and Email being invalid. Try adding a Name but not an Email; you should see the error messages about Email, but not Name. Try adding an invalid Email; you should see an error message stating that the email address is invalid. Finally, submit valid information, and you should be redirected with a success message just like before!
Summary
In this chapter, we covered the use of several Active Model modules and saw how we can use them to enhance our POROs (Plain Old Ruby Objects) with Active Record–type behaviors. We then learned that ActiveModel::Model includes a few of the most commonly used modules and most importantly gives our model what it needs to play nice with Action Pack and Action View conventions.
There are some Active Model modules we did not cover, and we didn’t exhaustively cover each module, but that’s okay. Knowing that Active Model exists and understanding the types of things it can do is good enough for now. When you’re ready for more information about Active Model, a good place to start is the official Rails guide, found at https://guides.rubyonrails.org/active_model_basics.html. Also, don’t be afraid to find the source code and look through it. It’s true that sometimes the source code may include things you don’t fully understand, but often you can get the basic idea. A lot of Rails code—Active Model included—is well designed and documented and is much more accessible than you might think. You might even learn some new tricks!
What’s next? We’ll take a brief look at Action Cable—an exciting component of Rails which uses WebSockets to add some “real-time” capabilities to our applications.