00:00:20.840
Okay, hi! I'm going to start the talk. I'm Shannon Skipper, and I'm here talking about backing a model with an API instead of a database.
00:00:28.619
So, let's get started. First off, this is an idea that is as old as Rails.
00:00:35.449
Active Resource is something that ships under the Rails repository. It was never made part of Rails, but what it does is allow you to point a model at a remote Rails app.
00:00:42.000
It could be localhost, but it could also be over the web. It will then use the expected Rails JSON API to serialize and deserialize over the wire.
00:00:55.230
So here you can do customer.new, customers.save, or customers.update—everything you would expect, but it's an API.
00:01:01.620
The downside is that it has to be a Rails JSON API, and that was an edge case that was not flexible enough to be merged into Rails' main code base.
00:01:19.920
There are plenty of people who use this in production; it's really useful if you just need a direct connection to another Rails database.
00:01:32.940
So what if you want to go to the basics and create something more flexible than Active Resource? Because you have an API, but it's not a Rails API; it's not conventional Rails JSON.
00:01:39.989
If we look at the model, we can start with a simple customer class. This class really doesn't do anything on its own.
00:01:52.520
So what do the Active Model docs say about what we can add? We have attribute assignments, callbacks, conversions, dirty tracking, and more.
00:02:00.209
One single include will actually get you a pretty large swath of these features, and that's Active Model::Model.
00:02:07.770
When you include Active Model::Model, this code from that module will automatically include the attribute assignments, validations, conversions, naming, and translations.
00:02:14.280
So why does it do that? It is for Action Pack and Action View support, so when you're using your model in your view, it knows the things that Rails needs to know about the model for the automatic parts to work.
00:02:25.440
However, it doesn't include attributes, callbacks, dirty tracking, JSON serialization, serializers, or lint tests, so let's look at those individually.
00:02:45.060
First, let's take a look at attributes. If we want to add the attributes API to a bare Ruby class, what does that look like?
00:02:51.420
The attributes API is something that was added in recent Rails versions—specifically Rails 5.2. This is when Active Model attributes were added.
00:03:06.810
In Rails 5.2, you can take a bare Ruby class, say Customer, include Active Model Attributes, and go ahead and set your attributes.
00:03:12.870
These are typed attributes; for example, the name is a string, and the age is an integer. If we add the attributes API, we can set our age using a string like '42', and it will correctly return the integer '42' when queried.
00:03:25.170
Let me show you some more examples of the attributes API.
00:03:31.590
For example, I'm going to use Square's Customer API; it's one of many Square APIs available that support businesses. We have customer attributes like their ID and creation source, both strings, as well as attributes like preferences, creation time, and birthday.
00:03:50.630
These date times come out of the box, and this array of passions is actually defined by me. You can take the types Rails gives you out of the box, or you can create your own types.
00:04:04.320
You can also set a default, such as an empty string, for all these string field attributes. Let's briefly look at creating custom attributes. This is done in the config initializers, where I created a types.rb file—just a convention for where you put your types.
00:04:53.849
In this file, I have a hash type and all you need to define is the cast method for the desired operation. For example, the hash type takes keys and makes them symbols, while the array of hashes type iterates through an array and maps their keys to symbols.
00:05:31.530
Now, looking back at the customers file, you can see that I defined the type for the array of hashes, which allows for coercion when fetching the record. This is what makes the Attributes API really powerful—it gives you database-like type coercion while using an arbitrary data source.
00:05:44.280
Next, let’s talk about callbacks. This is one feature you have to implement yourself; it's not automatic, but it is part of the Active Model.
00:05:57.419
You define the model callbacks you want to have; I've defined update and save. By including this module, your model gets the ability to run these callbacks.
00:06:08.849
For example, when performing save and save bang, you can run the callbacks before and after these operations. This is a way to implement Active Record-style callbacks in your API-backed model.
00:06:25.380
Now, let’s touch on conversion. You can include ActiveModel::Conversion to provide partial paths. This is part of what makes Action Pack and Action View work seamlessly.
00:06:49.139
Next, we have ActiveModel::Dirty, which is another feature you don't get automatically. You will first need to include it, then define the will_change method. This allows Rails to track changes in the attributes.
00:07:40.170
ActiveModel::Dirty offers methods that tell Rails when attribute values have changed. You also need to implement the changes method to alert the model of persisted changes. This method sets an instance variable to track if the model’s state has been updated, which is particularly handy when working with data from an API.
00:08:42.780
Let me give you a code demo to illustrate this. If we fetch a customer from the API, we can modify some attributes, like setting the given name to 'Shannon' and the company name to 'Square'. With the Attributes API, the customer will track these changes correctly.
00:09:24.420
If we check the changes, we’ll see that the given name changed from 'Amy' to 'Shannon', and the company name changed from an empty string to 'Square'. And if we call save, it triggers an update against the API.
00:10:15.120
We can also restore the family name directly or restore all attributes. The dirty tracking helps us manage these updates just like Active Record, enabling seamless interaction with API-backed models.
00:11:18.840
Moving on, we have serializers provided by Active Model. This requires explicit inclusion, but it lets you convert your model to hashes easily, which is essential for JSON handling.
00:11:37.200
Similarly, validations work just as you would expect. By including Active Model::Model, you get access to built-in validations. For example, I have defined an 'email' validation, which will check the format before the API is called.
00:13:00.500
With Active Model, we get naming and translation support too, which is beneficial for internationalization. It enhances how we interact with the model and improves user experience.
00:14:15.690
Additionally, there's a lint test you can include in your tests. By including ActiveModel::Lint, you get 35 assertions and simple checks to validate your model's compliance with Rails expectations.
00:14:35.640
It's beneficial for ensuring that your model meets Rails’ standards, which helps maintain consistency across your codebase.
00:15:50.310
In the context of pretty printing and inspecting, these features provide a clear visual structure for debugging and interacting with your API-backed models.
00:16:00.200
Pretty print effectively highlights immutable fields versus regular attributes, giving an intuitive view while working in a console or REPL, thus improving the overall modeling experience.
00:16:45.900
So when interacting with the API, nothing happens until the model is saved, which helps delineate the operations between local model management and remote data updating.
00:17:50.400
Throughout the demo, I’ve utilized the Square Connect Ruby gem, which allows seamless integration into Rails applications. This demonstrates how we employ the Ruby SDK to manage and synchronize models effectively.
00:18:38.320
After creating models with the discussed attributes and modules, Rails interactions via controllers operate similarly to usual database-backed Active Record methods, enhancing developer efficiency.
00:19:30.900
Using pagination, editing records, and managing CRUD operations mirror a standard Rails workflow, showing the capability of the API-backed models to function seamlessly within Rails’ conventions.
00:20:39.150
As the talk wraps up, the approach to building a robust API-backed model can be refined. Integrating Active Model gives you plenty of functionalities to work effectively with remote data.
00:21:32.920
If you have any questions or comments, please tweet at us at Square Dev on Twitter. I'm Shannon Skipper, an engineer and evangelist at Square, and I'm thrilled to be working on our public Ruby infrastructure.