Dynomite 2.0 Release: DynamoDB ActiveModel ORM
I spent a little time on Dynomite as part of working on the huge Jets 5 Release. This led to a Dynomite 2 release that’s ActiveModel Compatible.
In my first attempt at Dynomite 1, I thought that because DynamoDB is a key-value database, not a relational database, the interface would need to be different to reflect that. However, the more I used the v1 interface, the more I missed the ActiveModel interface. ActiveModel is simply too addicting.
Dynomite 2 is my humble attempt at making a DynamoDB ActiveRecord-like ORM library. Thanks to Rails abstracting the interface into ActiveModel, Dynomite can include the ActiveModel modules and define the required interface methods to make itself ActiveRecord-like.
Features
Let’s get into the meat and potatoes.
- ActiveRecord or ActiveModel Compatibility: Welcome back to ActiveRecord familarity. You get validations, callbacks, dirtiness, etc. You have familiar methods like
find
andfind_by
. - Relation Chainable Querying: Querying can be done with a chainable where method. You can do things like
all.where.limit.scan_index_backward
. I’ll cover a few examples below. - Simple migrate command: In v1, you had to specify the path of the migration file. This wasn’t very pleasant. In v2, the command is simple again:
jets dynamodb:migrate
. The state of whether or not a migration has been ran is now stored in aschema_migrations
table. - Indexes are Automatic: Indexes are discovered instead of defined by the model. This allows the querying interface to stay simple. It also allows any indexes added to be used automatically. You don’t have to change the code. It’s a more normal database workflow. IE: with MySQL, if you add an index, it automatically gets used. Imagine having to specify you’re always having to specify your indexes with SQL 🤣 Yes, explicitly. Pain. 😅
- Documentation: There’s improved documentation also. They are a part of the jets docs: Dynomite Docs
We’ll cover a few of the highlights in a little more detail next.
ActiveModel Compatibility
Dynomite is ActiveModel compatible. Thanks to this, you can use things like validations and callbacks. Here’s an example:
app/models/product.rb
class Product < ApplicationItem
field :category,
:product_id,
:product_name,
:price,
:stock_quantity
validates :price, presence: true
before_save :set_stock_quantity
def set_stock_quantity
self.stock_quantity ||= 1
end
end
Querying Examples
Here are some chain query examples.
Post.all.where(title: "title 1").limit(1).first
Post.all.where(title: "title 1").count
Note that the return object of querying methods like all
, where
, and limit
is a lazy Enumerator Relation. Since it’s lazy, AWS API calls will not be made to the DynamoDB service until the records are needed. Let’s take a closer look:
❯ jets console
> Post.where(title: "title 1").class
=> Dynomite::Item::Query::Relation
The Relation object contains attributes that will be used to run the query.
> Post.where(title: "title 1")
=> #<Dynomite::Item::Query::Relation:0x00007f615b4fcea0 @query={:where=>[{:title=>"title 1"}]}>
You can chain the where
methods.
> Post.where(title: "title 1").where(desc: "desc 1")
=> #<Dynomite::Item::Query::Relation:0x00007f615dc1b270 @query={:where=>[{:title=>"title 1"}, {:desc=>"desc 1"}]}>
You can then chain additional methods. More Docs: Querying Where
DynamoDB Dynomite Comparisons Functions and Querying Expressions
You can use comparison options like lt
and gt
. Here are some examples:
Product.where(category: "Electronics").and("price.lt": 1000)
Product.where(category: "Electronics").and.where("price.lt": 1000)
Product.where(category: "Electronics").and.where.not("price.lt": 1000)
You can also use DynamoDB native “functions”.
Product.where(category: "Electronics").attribute_exists('pictures.slide_view')
Product.attribute_exists('pictures.slide_view')
Product.attribute_not_exists('pictures.slide_view')
Product.attribute_type('pictures.slide_view', :string)
Product.begins_with("category", "Elect") # works for String
Product.contains("tags", "foo") # works for String, Set and List
Product.size_fn("category.gt", 100)
More info Querying Expressions
Find By
The familiar find_by
is available.
Product.find_by(category: "Electronics", sku: 101)
Product.find_by(category: "Electronics")
Product.find_by(category: "Electronics", sku: 101, name: "Smartphone", price: 500, stock_quantity: 50)
Product.find_by(price: 500, stock_quantity: 50)
Note that Dynomite will discover indexes and use them when available.
More info find_by docs
Assocations
Associations are also supported.
app/models/user.rb
class User < ApplicationItem
has_many :posts
end
There is typically a corresponding belongs_to as the inverse relationship.
app/models/post.rb
class Post < ApplicationItem
belongs_to :user
end
Note, there are some limits with associations since it’s not implemented with join tables, but as a field in the existing tables. Remember, DynamoDB is a document-based database, not a relational database.
More info Associations Docs
Summary
Dynomite 2 is a significant update. The major updates are ActiveModel Compatibility and Relation Chainable Querying. The migrate command is also simplified. The automatic behavior, like indexes, makes using DynamoDB easier. There’s even association support. For more information, check out the Dynomite Docs.
I also wanted to note that I did study and try a few other libraries. They were not for me. Here are comparisons of them here: Dynomite Vs Others
Thanks for reading this far. If you found this article useful, I'd really appreciate it if you share this article so others can find it too! Thanks 😁 Also follow me on Twitter.
Got questions? Check out BoltOps.
You might also like
More tools:
-
Kubes
Kubes: Kubernetes Deployment Tool
Kubes is a Kubernetes Deployment Tool. It builds the docker image, creates the Kubernetes YAML, and runs kubectl apply. It automates the deployment process and saves you precious finger-typing energy.
-
Jets
Jets: The Ruby Serverless Framework
Ruby on Jets allows you to create and deploy serverless services with ease, and to seamlessly glue AWS services together with the most beautiful dynamic language: Ruby. It includes everything you need to build an API and deploy it to AWS Lambda. Jets leverages the power of Ruby to make serverless joyful for everyone.
-
Lono
Lono: The CloudFormation Framework
Building infrastructure-as-code is challenging. Lono makes it much easier and fun. It includes everything you need to manage and deploy infrastructure-as-code.