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, andday— fordate_timeanddatefieldshour,minute, andsecond— fordate_timefields 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
#limitmethod and an#offsetmethod. 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
#updatequery 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_columnsmethod. - It is now possible to easily create or update records in a single call by using the
#update_or_createquery 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
Marten::HTTP::UploadedFileobjects can now be serialized to JSON.- A convenient
#remote_ip_addressmethod was added to theMarten::HTTP::Requestclass, allowing to easily retrieve the remote IP address of the incoming request.
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
#flashmethod. See Flash messages for more details. - The ability to bind the server to a Unix socket was introduced by adding a new
socketsetting. - A new
main_app_labelsetting was introduced, allowing to configure the label of the main application. - The
migratemanagement command now accepts a--checkoption that exits with a non-zero status when unapplied migrations exist, without applying them. - The
migratemanagement command now accepts a--pruneoption that deletes nonexistent migrations from themarten_migrationstable. - The
genmigrationsmanagement command now accepts a--checkoption that exits with a non-zero status when model changes require migrations, without generating migration files. - The
genmigrationsmanagement command now accepts a--dry-runoption that shows migrations that would be generated without writing them. - The
routesmanagement command now accepts a--grepoption that allows to only display routes whose path or name contains the given substring (case-insensitive).