Skip to main content
Version: Next

Marten 0.7.0 release notes

Under development.

Requirements and compatibility

  • Crystal: 1.17, 1.18, 1.19, and 1.20.
  • Databases:
    • MariaDB 10.11 and higher.
    • MySQL 8.4 and higher.
    • PostgreSQL 15 and higher.
    • SQLite 3.37.0 and higher.

New features

Polymorphic relationships

Polymorphic relationships can now be defined through the use of polymorphic fields. Those are useful when you want to store a reference to a record whose model can vary among a predefined set of possible types.

For example:

class Article < Marten::Model
field :id, :big_int, primary_key: true, auto: true
field :title, :string, max_size: 128
end

class Recipe < Marten::Model
field :id, :big_int, primary_key: true, auto: true
field :title, :string, max_size: 128
end

class Comment < Marten::Model
field :id, :big_int, primary_key: true, auto: true
field :target, :polymorphic, to: [Article, Recipe], related: :comments
field :text, :text
end

In the above example, a Comment record could be associated with an Article or a Recipe record, and each of these models could have many associated Comment records.

Helper methods and convenient scopes are also generated for polymorphic fields, allowing to interact with the related records in a type-safe manner. For example:

# Create an article
article = Article.create!(title: "This is an article")

# Create a recipe
recipe = Recipe.create!(title: "This is a recipe")

# Create a comment
comment = Comment.create!(text: "This is a comment", target: article)

# Regular getter methods
comment.target # => #<Article:0x1036e3ee0 id: 1, title: "This is an article">
comment.target_type # => "Article"
comment.target_id # => 1

# Type class getter method
comment.target_class # => Article (or nil if no related record is set)
comment.target_class! # => Article (or raise if no related record is set)

# Predicate helper methods
comment.article_target? # => true
comment.recipe_target? # => false

# Typed getters methods
comment.article_target # => Returns the associated Recipe record if the targeted record is indeed a Recipe record (or nil otherwise)
comment.article_target! # => Returns the associated Recipe record if the targeted record is indeed a Recipe record (or raise otherwise)

# Type-specific model scopes (generated based on the specified type classes)
Comment.with_article_target # => Returns all the comments associated with Article records
Comment.with_recipe_target # => Returns all the comments associated with Recipe records

Please refer to Polymorphic relationships to learn more about this new capability.

Email attachments

Marten emails can now include attachments through a dedicated #attach method, making it easy to attach files from disk, existing File objects, or arbitrary IOs.

For example:

class WelcomeEmail < Marten::Email
to @user.email
subject "Hello!"
template_name "emails/welcome_email.html"

before_deliver :add_attachments

def initialize(@user : User)
end

private def add_attachments
# Attach a file from a path.
attach "public/docs/terms.pdf"

# Attach generated content from an IO.
attach generate_pdf_io(@user), filename: "welcome.pdf", mime_type: "application/pdf"
end
end

Please refer to the Defining attachments section to learn more about this feature.

Field transformations

Query sets can now filter on date and date/time components through field transformations. These lookups apply the extraction at the SQL level (for example with strftime on SQLite or EXTRACT on PostgreSQL) instead of loading and comparing values in Crystal.

The following transformations are available:

  • year, month, and day — for date_time and date fields
  • hour, minute, and second — for date_time fields only

They can be combined with the usual field predicates. For example:

Article.filter(released_on__year: 2022)
Article.filter(created_at__year__gte: 2022)
Article.filter(created_at__hour__lt: 12)
Article.filter(created_at__month__in: [11, 12])
Article.filter(created_at__year__isnull: false)

Please refer to the field transformations reference for the full list of supported lookups.

Template whitespace control

The Marten templating language now supports whitespace control through the use of hyphens in tag, variable, and comment delimiters. Adding a hyphen (-) immediately after an opening delimiter or immediately before a closing delimiter allows to strip adjacent whitespace (spaces, tabs, and newlines) from the rendered output.

For example:

{%- if username == "John Doe" -%}
Wow, {{ username -}} , you have a long name!
{%- else -%}
Hello there!
{%- endif %}

The above template would render Wow, John Doe, you have a long name! when username is set to John Doe, and Hello there! otherwise — without the indentation and extra newlines from the template source.

Please refer to the Whitespace control section for more information.

Minor features

Models and databases

  • Query sets now provide a #limit method and an #offset method. Those methods respectively allow to limit the number of records returned and to offset the records returned. They act as aliases for the #[] method with a range.
  • The #update query set method can now be called on model classes directly, thus allowing to update all the records of the considered model with the specified values.
  • It is now possible to update only specific columns without running validations or callbacks for a model record by using the #update_columns method.
  • It is now possible to easily create or update records in a single call by using the #update_or_create query set method. An #update_or_create! variant is also available for the same purpose, but it raises in case updated/created records are invalid.
  • Query sets can now be filtered with enum values directly.

Handlers and HTTP

Templates

  • Most informational string properties (such as #ascii_only?, #blank?, #bytesize, #empty?, #size, and #valid_encoding?) are now available as template attributes for string values and safe string values.

Development

  • The flash store can now be accessed from the spec client by using the #flash method. See Flash messages for more details.
  • The ability to bind the server to a Unix socket was introduced by adding a new socket setting.
  • A new main_app_label setting was introduced, allowing to configure the label of the main application.
  • The migrate management command now accepts a --check option that exits with a non-zero status when unapplied migrations exist, without applying them.
  • The migrate management command now accepts a --prune option that deletes nonexistent migrations from the marten_migrations table.
  • The genmigrations management command now accepts a --check option that exits with a non-zero status when model changes require migrations, without generating migration files.
  • The genmigrations management command now accepts a --dry-run option that shows migrations that would be generated without writing them.
  • The routes management command now accepts a --grep option that allows to only display routes whose path or name contains the given substring (case-insensitive).