Объект Маршаллинг в Ruby
Coding
Что такое Маршаллинг объектов?
Когда вы пишете код, вы можете сохранить объект и передать его другой программе или использовать его при следующем выполнении программы.
Маршаллинг объекта используется, например, в Sidekiq; когда задание Sidekiq ставится в очередь в приложении Ruby on Rails, то в Redis вставляется сериализация этого задания, представляющая собой не что иное, как объект.
Затем процесс Sidekiq может десериализовать этот JSON и восстановить исходную работу из JSON.
В компьютерном программировании этот процесс сериализации и десериализации объекта является тем, что мы обычно называем маршалингом объекта(англ. object marshalling). Теперь давайте посмотрим на то, что Ruby изначально предоставляет для обработки Object Marshalling.
Модуль маршала
Поскольку Ruby является полностью объектно-ориентированным языком программирования, он предоставляет способ сериализации и хранения объектов с использованием модуля Marshall в его стандартной библиотеке.
Это позволяет сериализовать объект в поток байтов, который можно сохранить и десериализовать в другом процессе Ruby.
Итак, давайте сериализуем строку и более подробно рассмотрим сериализованный объект.
hello_world = 'hello world!' serialized_string = Marshal.dump(hello_world) # => "\x04\bI\"\x11hello world!\x06:\x06ET" serialized_string.class # => String deserialized_hello_world = Marshal.load(serialized_string) # => "hello world!" hello_world.object_id # => 70204420126020 deserialized_hello_world.object_id # => 70204419825700
Затем мы вызываем Marshal.dump метод модуля для сериализации нашей строки. Мы сохраняем возвращаемое значение, которое содержит нашу сериализованную строку, в переменной serialized_string.
Эта строка может быть сохранена в файле, и файл может быть повторно использован для восстановления исходного объекта в другом процессе.
Затем мы вызываем метод Marshal.load для восстановления исходного объекта из потока байтов.
Мы можем видеть, что эта только что восстановленная строка имеет другой object_id, чем строка hello_world, что означает, что это другой объект, но он содержит те же данные.
Довольно круто! Но как модуль Marshalможет восстановить строку? И что, если я хочу контролировать, какие атрибуты сериализовать и десериализовать?
Конкретный пример маршаллинга объектов
Чтобы ответить на эти вопросы, давайте реализуем стратегию сортировки в пользовательской структуре с именем User.
User = Struct.new(:fullname, :age, :roles)
user = User.new('Mehdi Farsi', 42, [:admin, :operator])
Структура Userопределяет 3 атрибута: fullname, ageи roles. Для этого примера у нас есть бизнес-правило, в котором мы сериализуем, только если оно соответствует следующим критериям:
Fullnameсодержит менее 64 символов- Массив role не содержит роль
:admin
Для этого мы можем определить метод User#marshal_dump для реализации нашей пользовательской стратегии сериализации.
Этот метод будет вызван, когда мы вызовем метод Marshal.dump с экземпляром пользовательской структуры в качестве параметра. Давайте определим этот метод:
User = Struct.new(:age, :fullname, :roles) do
def marshal_dump
{}.tap do |result|
result[:age] = age
result[:fullname] = fullname if fullname.size <= 64
result[:roles] = roles unless roles.include? :admin
end
end
end
user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
user_dump = Marshal.dump(user) # 'in User#marshal_dump'
user_dump # => "\x04\bU:\tUser{\a:\bageI\"\x10Mehdi Farsi\x06:\x06ET:\rfullnamei/"
В приведенном выше примере мы видим, что наш метод User#marshal_dump вызывается, когда мы вызываем Marshal.dump (user). Переменная user_dumpсодержит строку, которая является сериализацией нашего экземпляра User.
Теперь, когда у нас есть дамп, давайте десериализовать его для восстановления нашего пользователя.
Для этого мы определяем метод User#marshal_load, который отвечает за реализацию стратегии десериализации пользовательского дампа.
Итак, давайте определим этот метод.
User = Struct.new(:age, :fullname, :roles) do
def marshal_dump
{}.tap do |result|
result[:age] = age
result[:fullname] = fullname if fullname.size <= 64
result[:roles] = roles unless roles.include? :admin
end
end
def marshal_load(serialized_user)
self.age = serialized_user[:age]
self.fullname = serialized_user[:fullname]
self.roles = serialized_user[:roles] || []
end
end
user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
user_dump = Marshal.dump(user) # 'in User#marshal_dump'
user_dump # => "\x04\bU:\tUser{\a:\bagei/:\rfullnameI\"\x10Mehdi Farsi\x06:\x06ET"
original_user = Marshal.load(user_dump) # 'in User#marshal_load'
original_user # => #<struct User age=42, fullname="Mehdi Farsi", roles=[]>
В приведенном выше примере мы видим, что наш User#marshal_load method вызывается, когда мы вызываем Marshal.load(user_dump). Переменная original_user содержит структуру, которая является восстановлением нашего пользовательского экземпляра.
Обратите внимание, что original_user.roles не похож на массив user.roles, поскольку во время сериализации user.roles включал роль :admin. Таким образом, user.roles не был преобразован в переменную user_dump.
Методы _dump и self._load
Когда вызываются Marshal.dump и Marshal.load, эти методы вызывают методы marshal_dump и marshal_load для объекта, переданного в качестве параметра этих методов.
Но что, если я скажу вам, что методы Marshal.dump и Marshal.load пытаются вызвать два других метода с именами _dump и self._load для объекта, переданного в качестве параметра?
_Dump метод
Различия между методами marshal_dump и _dump:
- вам нужно обработать стратегию сериализации на более низком уровне при использовании метода
_dump- вам нужно вернуть строку, которая представляет данные для сериализации - метод
marshal_dumpимеет приоритет перед_dump, если определены оба
Давайте посмотрим на следующий пример:
User = Struct.new(:age, :fullname, :roles) do
def _dump level
[age, fullname].join(':')
end
end
user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
Marshal.dump(user) # => "\x04\bIu:\tUser\x1342:Mehdi Farsi\x06:\x06EF"
В методе User#_dump мы должны создать экземпляр и вернуть объект сериализации - строку, которая представляет вашу сериализацию.
В следующем примере мы определяем методы User#marshal_dump и User#_dump и возвращаем строку, чтобы увидеть, какой метод вызывается
User = Struct.new(:age, :fullname, :roles) do
def marshal_dump
'in User#marshal_dump'
end
def _dump level
'in User#_dump'
end
end
user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
user_dump = Marshal.dump(user) # "in User#marshal_dump"
Мы видим, что вызывается только User#marshal_dump, хотя они оба определены.
Метод self._load
Теперь давайте посмотрим на методы marshal_load и_load.
Различия между методами marshal_load и _load:
- Вы должны обрабатывать стратегию десериализации на более низком уровне при использовании метода
_load- вы отвечаете за создание исходного объекта. - Метод
marshal_loadпринимает десериализованный объект в качестве аргумента, когда метод_self.loadпринимает в качестве аргумента сериализованную строку. - Метод
marshal_loadявляется методом экземпляра, когда self._load является методом класса.
Давайте посмотрим на следующий пример:
User = Struct.new(:age, :fullname, :roles) do
def _dump level
[age, fullname].join(':')
end
def self._load serialized_user
user_info = serialized_user.split(':')
new(*user_info, Array.new)
end
end
user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
user_dump = Marshal.dump(user)
user_dump # => "\x04\bIu:\tUser\x1342:Mehdi Farsi\x06:\x06EF"
original_user = Marshal.load(user_dump)
original_user # => #<struct User age="Mehdi Farsi", fullname=42, roles=[]>
В методе User._load:
- Мы десериализовываем строку, возвращаемую методом
User#_dump - Мы создаем нового
User, передавая десериализованную информацию
Мы видим, что мы отвечаем за выделение и создание экземпляров объекта, используемого для восстановления нашего первоначального пользователя.
Таким образом, Marshal.load, связанный с Marshal.load, заботится о создании восстановленного исходного объекта. Затем он вызывает метод marshal_load с сериализованным объектом, передаваемым в качестве аргумента только что созданному объекту.
Напротив, вызов Marshal.load в сочетании с _load позволяет методу класса self._load отвечать за:
- десериализация данных, возвращаемых методом _dump
- создание восстановленного исходного объекта
Заключение
В зависимости от ваших потребностей, вы можете принять решение о реализации более высокой или более низкой стратегии сериализации / десериализации. Для этого вы можете использовать модуль Marshal, связанный с подходящими методами Marshal hook.

Заметили ошибку или есть вопросы?Рассказывай нам о них,связаться с нами ты сможешь с помощью нашего чата или же с помощью бота обратной связь
Не забывайте ставить 👍 если вам понравилась и подписаться на канал,а так же посетить наш чат.