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. stringfields now support amin_sizeoption 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.optionsdatabase setting. - It is now possible to define
many_to_oneandone_to_onefields that target models with non-integer primary key fields (such as UUID fields for example).
Handlers and HTTP
- The
Marten::Handlers::RecordListgeneric 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#querysetmacro. - A new
Marten::Middleware::AssetServingmiddleware 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::SSLRedirectmiddleware was introduced to allow redirecting non-HTTPS requests to HTTPS easily. - A new
Marten::Middleware::ContentSecurityPolicymiddleware 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::I18nmiddleware can now automatically determine the current locale based on the value of a cookie whose name can be configured with thei18n.locale_cookie_namesetting. - The
Marten::Middleware::I18nmiddleware now automatically sets the Content-Language header based on the activated locale.
Templates
- A
jointemplate filter was introduced to allow converting enumerable template values into a string separated by a separator value. - A
splittemplate 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::SuspiciousOperationexceptions 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_hostssetting).Marten#setupnow raises.Marten::Conf::Errors::InvalidConfigurationexceptions when a configured database involves a backend that is not installed (eg. a MySQL database configured withoutcrystal-lang/crystal-mysqlinstalled and required).- The
newmanagement command now automatically creates a.editorconfigfile for new projects. - A new
root_pathsetting 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
--planoption was added to themigratemanagement 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
newmanagement command: if thetypeandnamearguments 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_aliaseshelper 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
#regexmethod and can no longer rely on a#regexmacro to generate such method. - The generic handlers that used to require the use of a
#modelclass 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
#schemaclass 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.