Вот, допустим, есть такой код:
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: таки запилил гем .