Marten 0.5: Relations pre-fetching, model scopes, enum fields, and more!

Posted by Morgan Aubert on Jul 13, 2024

Morgan Aubert's picture

We are pleased to announce the release of Marten 0.5!

Marten 0.5 brings significant enhancements, including relations pre-fetching, model scopes, enum fields for models and schemas, and support for raw SQL predicates in query sets. These improvements enhance performance, flexibility, and ease of use. For a detailed overview of all new features and changes, check out the full changelog.

New features and highlights

Relations pre-fetching

Marten now provides the ability to prefetch relations when using query sets through the use of the new #prefetch method. When using #prefetch, the records corresponding to the specified relationships will be prefetched in single batches and each record returned by the original query set will have the corresponding related objects already selected and populated.

For example:

posts_1 = Post.all.to_a
# hits the database to retrieve the related "tags" (many-to-many relation)
puts posts_1[0].tags.to_a

posts_2 = Post.all.prefetch(:tags).to_a
# doesn't hit the database since the related "tags" relation was already prefetched
puts posts_2[0].tags.to_a

Like the existing #join method, this allows to alleviate N+1 issues commonly encountered when accessing related objects. However, unlike #join (which can only be used with single-valued relationships), #prefetch can be used with both single-valued relationships and multi-valued relationships (such as many-to-many relationships, reverse many-to-many relationships, and reverse many-to-one relationships).

Please refer to Pre-fetching relations to learn more about this new capability.

Model scopes

It is now possible to define scopes in model classes. Scopes allow to pre-define specific filtered query sets, which can be easily applied to model classes and model query sets.

Such scopes can be defined through the use of the #scope macro, which expects a scope name (string literal or symbol) as first argument and requires a block where the query set filtering logic is defined:

class Post < Marten::Model
  field :id, :big_int, primary_key: true, auto: true
  field :title, :string, max_size: 255
  field :is_published, :bool, default: false
  field :created_at, :date_time

  scope :published { filter(is_published: true) }
  scope :unpublished { filter(is_published: false) }
  scope :recent { filter(created_at__gt: 1.year.ago) }
end

Post.published # => Post::QuerySet [...]>

It is also possible to override the default scope through the use of the #default_scope macro. This macro requires a block where the query set filtering logic is defined:

class Post < Marten::Model
  field :id, :big_int, primary_key: true, auto: true
  field :title, :string, max_size: 255
  field :is_published, :bool, default: false
  field :created_at, :date_time

  default_scope { filter(is_published: true) }
end

Please refer to Scopes for more details on how to define scopes.

Enum field for models and schemas

It is now possible to define enum fields in models and schemas. For models, such fields allow you to store valid enum values, with validation enforced at the database level. When validating data with schemas, they allow you to expect valid string values that match those of the configured enum.

For example:

enum Category
  NEWS
  BLOG
end

class Article < Marten::Model
  field :id, :big_int, primary_key: true, auto: true
  field :category, :enum, values: Category
end

article = Article.last!
article.category # => Category::BLOG

Raw SQL predicate filtering

Marten now provides the ability to filter query sets using raw SQL predicates through the use of the #filter method. This is useful when you want to leverage the flexibility of SQL for specific conditions, but still want Marten to handle the column selection and query building for the rest of the query.

For example:

Author.filter("first_name = :first_name", first_name: "John")
Author.filter("first_name = ?", "John")
Author.filter { q("first_name = :first_name", first_name: "John") }

Please refer to Filtering with raw SQL predicates to learn more about this new capability.

Other changes

Please head over to the official Marten 0.5 release notes for an overview of all the changes that are part of this release.