Skip to main content
Version: 0.4

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 a min_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 of Enumerable#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 and one_to_one fields that target models with non-integer primary key fields (such as UUID fields for example).

Handlers and HTTP

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 the allowed_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 without crystal-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 the migrate 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 the type and name 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

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.