Marten 0.3.0 release notes
June 19, 2023.
Requirements and compatibility
Crystal 1.6, 1.7, and 1.8.
New features
Support for streaming responses
It is now possible to generate streaming responses from iterators of strings easily by leveraging the Marten::HTTP::Response::Streaming
class or the #respond
helper method. This can be beneficial if you intend to generate lengthy responses or responses that consume excessive memory (a classic example of this is the generation of large CSV files).
Please refer to Streaming responses to learn more about this new capability.
Caching
Marten now lets you interact with a global cache store that allows interacting with an underlying cache system and performing basic operations such as fetching cached entries, writing new entries, etc. By using caching, you can save the result of expensive operations so that you don't have to perform them for every request.
The global cache can be accessed by leveraging the Marten#cache
method. Here are a few examples on how to perform some basic caching operations:
# Fetching an entry from the cache.
Marten.cache.fetch("mykey", expires_in: 4.hours) do
"myvalue"
end
# Reading from the cache.
Marten.cache.read("unknown") # => nil
Marten.cache.read("mykey") # => "myvalue"
Marten.cache.exists?("mykey") => true
# Writing to the cache.
Marten.cache.write("foo", "bar", expires_in: 10.minutes) => true
Marten's caching leverages a cache store mechanism. By default, Marten uses an in-memory cache (instance of Marten::Cache::Store::Memory
) and other third-party stores can be installed depending on your caching requirements (eg. Memcached, Redis).
Marten's new caching capabilities are not only limited to its standard cache functionality. They can also be effectively utilized via the newly introduced template fragment caching feature, made possible by the cache
template tag. With this feature, specific parts of your templates can now be cached with ease.
Please refer to the Caching to learn more about these new capabilities.
JSON field for models and schemas
Marten now provides the ability to define json
fields in models and schemas. These fields allow you to easily persist and interact with valid JSON structures that are exposed as JSON::Any
objects by default.
For example:
class MyModel < Marten::Model
# Other fields...
field :metadata, :json
end
MyModel.last!.metadata # => JSON::Any object
Additionally, it is also possible to specify that JSON values must be deserialized using a class that makes use of JSON::Serializable
. This can be done by leveraging the serializable
option in both model fields and schema fields.
For example:
class MySerializable
include JSON::Serializable
property a : Int32 | Nil
property b : String | Nil
end
class MyModel < Marten::Model
# Other fields...
field :metadata, :json, serializable: MySerializable
end
MyModel.last!.metadata # => MySerializable object
Duration field for models and schemas
It is now possible to define duration
fields in models and schemas. These allow you to easily persist valid durations (that map to Time::Span
objects in Crystal) in your models but also to expect valid durations in data validated through the use of schemas.
For example:
class Recipe < Marten::Model
field :id, :big_int, primary_key: true, auto: true
# Other fields...
field :fridge_time, :duration, blank: true, null: true
end
Minor features
Models and databases
- New
#get_or_create
/#get_or_create!
methods were added to query sets in order to allow easily retrieving a model record matching a given set of filters or creating a new one if no record is found. string
fields now support amin_size
option allowing to validate the minimum size of persisted string field values.- A new
#includes?
method was added to query sets in order easily perform membership checks without loading the entire list of records targeted by a given query set. - Alternative
#exists?
methods were added to query sets in order to allow specifying additional filters to use as part of existence checks. - An
#any?
method was added to query sets in order to short-circuit the default implementation ofEnumerable#any?
and to avoid loading the full list of records in memory (when called without arguments). This overridden method is technically an alias of#exists?
. - Marten migrations are now optimized to prevent possible issues with circular dependencies within added or deleted tables
- It is now possible to define arbitrary database options by using the new
db.options
database setting. - It is now possible to define
many_to_one
andone_to_one
fields that target models with non-integer primary key fields (such as UUID fields for example).
Handlers and HTTP
- The
Marten::Handlers::RecordList
generic record now provides the ability to specify a custom query set instead of a model class. This can be achieved through the use of the#queryset
macro. - A new
Marten::Middleware::AssetServing
middleware was introduced to make it easy to serve collected assets in situations where it is not possible to easily configure a web server (such as Nginx) or a third-party service (like Amazon's S3 or GCS) to serve assets directly. - A new
Marten::Middleware::SSLRedirect
middleware was introduced to allow redirecting non-HTTPS requests to HTTPS easily. - A new
Marten::Middleware::ContentSecurityPolicy
middleware was introduced to ensure the presence of the Content-Security-Policy header in the response's headers. Please refer to Content Security Policy to learn more about the Content-Security-Policy header and how to configure it. - The
Marten::Middleware::I18n
middleware can now automatically determine the current locale based on the value of a cookie whose name can be configured with thei18n.locale_cookie_name
setting. - The
Marten::Middleware::I18n
middleware now automatically sets the Content-Language header based on the activated locale.
Templates
- A
join
template filter was introduced to allow converting enumerable template values into a string separated by a separator value. - A
split
template filter was introduced to allow converting a string into an array of elements.
Schemas
- Type-safe getter methods (ie.
#<field>
,#<field>!
, and#<field>?
) are now automatically generated for schema fields. Please refer to Accessing validated data in the schemas documentation to read more about these methods and how/why to use them.
Development
Marten::HTTP::Errors::SuspiciousOperation
exceptions are now showcased using the debug internal error page handler to make it easier to diagnose errors such as unexpected host errors (which result from a missing host value in theallowed_hosts
setting).Marten#setup
now raises.Marten::Conf::Errors::InvalidConfiguration
exceptions when a configured database involves a backend that is not installed (eg. a MySQL database configured withoutcrystal-lang/crystal-mysql
installed and required).- The
new
management command now automatically creates a.editorconfig
file for new projects. - A new
root_path
setting was introduced to make it possible to configure the actual location of the project sources in your system. This is especially useful when deploying projects that have been compiled in a different location from their final destination, which can happen on platforms like Heroku. By setting the root path, you can ensure that your application can find all necessary project sources, as well as other files like locales, assets, and templates. - A new
--plan
option was added to themigrate
management command in order to provide a comprehensive overview of the operations that will be performed by the applied or unapplied migrations. - An interactive mode was added to the
new
management command: if thetype
andname
arguments are not provided, the command now prompts the user for inputting the structure type, the app or project name, and whether the auth app should be generated. - It is now possible to specify command aliases when defining management commands by leveraging the
#command_aliases
helper method.
Security
- The ability to fully configure and customize the Content-Security-Policy header was added to the framework. Please refer to Content Security Policy to learn more about the Content-Security-Policy header and how to configure it in Marten projects.
Deployment
- A new guide was added in order to document how to deploy on Heroku.
- A new guide was added in order to document how to deploy on Fly.io.
Backward incompatible changes
Handlers and HTTP
- Custom route parameter must now implement a
#regex
method and can no longer rely on a#regex
macro to generate such method. - The generic handlers that used to require the use of a
#model
class method now leverage a dedicated macro instead. This is to make handlers that inherit from generic handler classes more type-safe when it comes to manipulating model records. - The generic handlers that used to require the use of a
#schema
class method now leverage a dedicated macro instead. This is to make handlers that inherit from generic handler classes more type-safe when it comes to manipulating schema instances.