Rails 7.1 Adds Support for MessagePack as Message Serializer

MessagePack is now supported in Rails 7.1, this new serializer enables the generation of smaller payloads and significantly faster serialization and deserialization compared to other serializers.

November 15, 2023 - 4 minute read -
Rails

This post is also published in blog.saeloun.com.

In Rails 7.1, support for MessagePack has been added, and it can be used with MessageEncryptor and MessageVerifier. config.active_support.message_serializer will also accept :message_pack and :message_pack_allow_marshal as serializers.

What is MessagePack?

MessagePack is an efficient binary serialization format that enables the exchange of data among multiple languages, similar to JSON.

It is faster and more compact compared to JSON, as it is optimized for binary data serialization. Specifically designed for efficiently representing complex data structures, it makes the payload smaller and faster to serialize and deserialize.

The following is a message serialized by MessagePack:

require 'msgpack'
msg = [1,2,3].to_msgpack  #=> "\x93\x01\x02\x03"
MessagePack.unpack(msg)   #=> [1,2,3]

Reference

Unlike BSON, which is a similar serialization format and less verbose, BSON is designed for faster in-memory manipulation, whereas MessagePack is designed for efficient network communication.

Before

Before Rails 7.1, Rails didn’t have native support for serializing using MessagePack. We had to install the msgpack gem and serialize the message.

# app/services/secure_message_service.rb

class SecureMessageService
  def self.encrypt(message)
    message_encryptor.encrypt_and_sign(message)
  end

  def self.decrypt(encrypted_message)
    message_encryptor.decrypt_and_verify(encrypted_message)
  end

  private

  def self.message_encryptor
    secret_key = ActiveSupport::KeyGenerator.new(Rails.application.secret_key_base).generate_key('salt', 32)
    ActiveSupport::MessageEncryptor.new(secret_key, serializer: MessagePack)
  end
end
# rails c

> encrypted_message = SecureMessageService.encrypt([{:key=>"value"}, [1, 2, 3], {:a=>{:b=>1}}])

=> "AaeKemIH/k7moSB8kgGvsNPJNr7MY1vUlMlwGBk=--AMViwItIvTcwz9Xp--NTMQJJrKKgdA1y1qt/KuWA=="

> SecureMessageService.decrypt(encrypted_message)

=> [{:key=>"value"}, [1, 2, 3], {:a=>{:b=>1}}]

To use MessagePack with ActiveSupport::MessageEncryptor, we need to pass serializer argument with the value MessagePack. This ensures that the messages are serialized and deserialized appropriately during both encryption and decryption processes.

After:

MessagePack is supported starting from Rails 7.1, with msgpack integrated into it. Rails has also introduced a new class called ActiveSupport::MessagePack, designed to serialize most basic Ruby data types.

To configure MessagePack as the default serializer in the Rails application, include the following in config/application.rb:

# config/application.rb

config.active_support.message_serializer = :message_pack

In Rails 7.1, the default serializer is json_allow_marshal. However, it can fall back to deserializing with Marshal so that legacy messages can still be read. We can set MessagePack as the default serializer in the app by using the config.active_support.message_serializer configuration method and setting the value as message_pack.

# app/services/secure_message_service.rb

class SecureMessageService
  def self.encrypt(message)
    message_encryptor.encrypt_and_sign(message)
  end

  def self.decrypt(encrypted_message)
    message_encryptor.decrypt_and_verify(encrypted_message)
  end

  private

  def self.message_encryptor
    secret_key = ActiveSupport::KeyGenerator.new(Rails.application.secret_key_base).generate_key('salt', 32)
    ActiveSupport::MessageEncryptor.new(secret_key)
  end
end

Since MessagePack is configured as the message serializer using the config.active_support.message_serializer method, we do not need to pass the serializer argument to ActiveSupport::MessageEncryptor as we did in the previous Rails version.

# rails c

> encrypted_message = SecureMessageService.encrypt([{:key=>"value"}, [1, 2, 3], {:a=>{:b=>1}}])

=> "AaeKemIH/k7moSB8kgGvsNPJNr7MY1vUlMlwGBk=--AMViwItIvTcwz9Xp--NTMQJJrKKgdA1y1qt/KuWA=="

> SecureMessageService.decrypt(encrypted_message)

=> [{:key=>"value"}, [1, 2, 3], {:a=>{:b=>1}}]

> ActiveSupport::Messages::Codec.default_serializer

=> :message_pack

MessageEncryptor will serialize the message using MessagePack during both encryption and decryption processes. To check the default serializer, we can run ActiveSupport::Messages::Codec.default_serializer, which returns message_pack.

Along with message_serializer, MessagePack can also be used as cookies_serializer and cache_serializer.