Data.Maybe.rb
Вот, допустим, есть такой код:
def authenticate_user_from_token!
return unauthorized! unless request.headers['Authorization'].present?
token = request.headers['Authorization'].split(' ').last
payload = JsonWebToken.decode(token)
return unauthorized! unless payload.present?
user_id = payload['user_id']
return unauthorized! unless user_id.present?
user = User.find_by(id: user_id)
return unauthorized! unless user.present?
@current_user = user
end
Очень плохо, отвратительно. Нужно срочно расчехлить Maybe/Some/Option:
def authenticate_user_from_token!
@current_user =
Maybe(request)
.fmap { |request | request.headers['Authorization'] }
.fmap { |auth_header| auth_header.split(' ').last }
.fmap { |token | JsonWebToken.decode(token) }
.fmap { |payload | payload['user_id'] }
.fmap { |user_id | User.find_by(id: user_id) }
.from_just { unauthorized!; nil }
end
Мммм, монады.
В интернете 100500 реализаций Maybe, но мы пойдём иным путём, в силу того, что критерий для Nothing у нас в этом конкретном случае специфичен для Rails (.present?
я имею в виду). Вместо того, чтобы манкипатчить чужие гемы, лучше написать немного своего ненужного кода.
Maybe = Struct.new(:value) do
def fmap(&f)
if value.present?
Maybe.new(f.(value))
else
self
end
end
def from_just(&when_nothing)
if value.present?
value
else
when_nothing.()
end
end
end
def Maybe(value)
Maybe.new(value)
end
Окей, тесты проходят. Но как-то не оопешно. Ифы повторяются, сплошной грех. Второй подход:
def Maybe(value)
if value.present?
Just.new(value)
else
Nothing
end
end
Just = Struct.new(:value) do
def fmap
Maybe(yield value)
end
def from_just
value
end
end
module Nothing
def self.fmap
self
end
def self.from_just
yield
end
end
ООПе-духи одобряют!
Единственный печальный момент - это наличие унылого .present?
. Заэкстрактим:
def Maybe(value, &is_just)
is_just ||= :itself.to_proc
if is_just.(value)
Just.new(value, is_just)
else
Nothing
end
end
Just = Struct.new(:value, :is_just) do
def fmap
Maybe(yield(value), &is_just)
end
def from_just
value
end
end
module Nothing
def self.fmap
self
end
def self.from_just
yield
end
end
Интерфейс несколько поменялся, но зато теперь эта штука не зависит от рельсов, абстракция не протекает и тесты будут работать супербыстро.
Maybe({user: {address: {street: '213'}}})
.fmap{|x| x[:user]}
.fmap{|x| x[:address]}
.fmap{|x| x[:street]}
.from_just { 'No such number, address unknown' }
# => "213"
Maybe({user: {address: ''}})
.fmap{|x| x[:user]}
.fmap{|x| x[:address]}
.fmap{|x| x[:street]}
.from_just { 'No such number, address unknown' }
# TypeError: no implicit conversion of Symbol into Integer
#OKAY, WHAT ABOUT THIS?#
Maybe({user: {address: ''}}, &:present?)
.fmap{|x| x[:user]}
.fmap{|x| x[:address]}
.fmap{|x| x[:street]}
.from_just { 'No such number, address unknown' }
# => "No such number, address unknown"
Победа!
UPD: таки запилил гем.