- Механизм валидации данных
- Модули
- Методы класса/модуля
- Структура каталогов Ruby-проекта
- Рефакторинг и распределение кода в нескольких файлах
Конечно же наши приложения захотят взаимодействовать с внешним миром, а значит и получать данные. Это может быть пользовательский ввод или API вызов к сервису. В любом случае, этап валидации (проверки) данных необходим.
Проектировать валидации стоит уже с момента проектирования класса. Когда вы определяете состояние класса, то есть переменные экземпляра, спросите себя: какие проверки над этими данными дложны быть?
Однако есть один подводный камень. В стремлении написать наиболее устойчивый код мы можем слишком сильно увлечься проверкой данных и начать писать методы для проверки всех возможных вариантов вообще. Этот путь долгий и почти наверняка какие-то варианты ускользнут от нашего внимания. Поэтому хорошей практикой является реализации только тех валидаций которые являются наиболее критическими для проекта, например: наличие email при регистрации пользователя, наличие идентификатора при изменении записи, проверка того что цена является числом. Обычно понимание того какие валидации вам необходимы приходит само собой когда вы определяете какой тип данных будете использовать для представления объекта реального мира. Если вы знаете как будет описываться объект в программе (какой у него тип данных), то скорее всего уже имеете представление каким этот объект может или не может быть.
Модули в Ruby используются очень широко. В основном модули служат двум основным задачам:
- Определению пространства имён
- Группировке методов для их последующего включения в один или более классов
Говоря о пространстве имён, можно посмотреть на многие существующие библиотеки и проекты: ActiveModel, Grape, Elasticsearch. Кроме того, при создании нового Rails приложения автоматически создаётся модуль с названием вашего приложения и все классы определяются внутри этого модуля.
Зачем всё это нужно? Прежде всего, для разрешения возможных проблем с конфликтом имён. Пусть, к примеру, вы используете фреймворк для создания API приложений Grape. Задача приложения в управлении винодельней и у вас конечно же присутствует модель Grape. Теперь в коде вы делаете вызов Grape
, какой класс или модуль Ruby должен использовать?
Для решения этой задачи мы можем обернуть наше приложение с помощью модуля Winery
. Тогда вызов Grape
будет обращаться к модулю фреймворка, а для того чтобы нам добратья до винограда необходимо сделать вызов Winery::Grape
. Теперь понятно что к чему относится.
Определение модуля и вложенного в него класса выглядит так:
module Winery
class Grape
end
end
Как вы уже обратили внимание, обращение ко вложенному классу происходит через ::
Важный момент, вы можете определить вложенный класс в совершенно другом файле. Например:
# winery.rb
require_relative 'winery/grape'
module Winery
end
# winery/grape.rb
module Winery
class Grape
end
end
Само собой, файл с определением класса Winery::Grape
должен быть подключён в файле winery.rb.
Наконец, определять можно не только классы внутри модулей, но и модули внутри модулей, классы внутри классов и даже модули внутри классов.
Использование модулей для группировки методов позволяет нам определить модуль с определённой функциональностью и затем использовать эту функциональность в разных классах. Валидации - отличный пример такого решения:
module ValidateLength
def validate_length value, options = {}
length = options[:length] || 10
raise ArgumentError unless value.length <= length
end
end
class User
include ValidateLength
def initialize name
@name = name if validate_length name
end
end
class Book
include ValidateLength
def initialize title
@title = title if validate_length title, length: 15
end
end
User.new 'Dan'
User.new 'Mihaly Csikszentmihalyi' # => ArgumentError
Book.new 'Excession'
Book.new 'The Player of Games' # => ArgumentError
В этом примере мы определяем модуль, который содержит метод validate_length
и затем подключив модуль в классы User
и Book
с помощью метода include
используем вызов метода validate_length
для проверки длины аргумента.
Всё что мы делали ранее в отношении определения методов позволяло нам вызывать эти методы у каких-либо объектов. Однако, принимая во внимание что класс или модуль в Ruby также является объектом, нет никаких препятствий чтобы определить метод непосредственно у класса или модуля.
Для этого при определении метода нам необходимо указать что метод определяется именно в контексте объекта модуля или класса. Мы можем сделать это с помощью self
:
module Winery
def self.year_of_foundation
1954
end
end
Winery.year_of_foundation # => 1954
После того как вы распределите код между несколькими файлами, необхоимо обеспечить подключение этих файлов. Например, пусть проект имеет такую структуру:
# winery.rb
module Winery
end
# winery/grape.rb
module Winery
class Grape
end
end
# winery/order.rb
module Winery
class Order
end
end
Для того чтобы создать объект винограда или заказа мы должны сделать вызов Winery::Grape.new
и Winery::Order.new
соответственно. Но это не будет работать потому что при исполнении приложения ruby winery.rb
интерпретатор ничего не знает о существовании других файлов в проекте. Поэтому эти другие файлы необходимо в проект подключить:
# winery.rb
require_relative 'winery/grape'
require_relative 'winery/order'
module Winery
end
# winery/grape.rb
module Winery
class Grape
end
end
# winery/order.rb
module Winery
class Order
end
end
Теперь всё в порядке.
Напишите модуль CalculateYears
для определения возраста (полных лет). Затем подключите его в классы User
и Winery
. Благодаря этому мы сможем узнать возраст пользователя или количество лет существования винодельни.
module CalculateYears
def years_old
# TODO: add some logic
end
end
class User
def initialize name, date_of_birth
end
end
class Winery
def initialize title, date_of_foundation
end
end
User.new('Julie', '1996-07-22').years_old # => 19
Winery.new('A Light Drop', '1954-08-01').years_old # => 61
Напишите приложение для игры в «Кости». Основные правила игры:
-
По умолчанию в игре используется 2 кубика с 6 гранями. Однако, необходимо предусмотреть возможность изменения числа кубиков и количества граней
-
В игре может принимать произвольное количество пользователей
-
Необходимо обеспечить проверку того что все пользователи, принимающие участие в игре сделали ставку
-
Также необходимо проверять валидна ли ставка (число очков соответствует возможному варианту очков которое может выпасть у кубиков, количество кредитов ставки не привышает общее количество кредитов пользователя)
-
По умолчанию выигрыш пользователя составляет 25% от его ставки (требуется предусмотреть простую возможность изменения этой величины)
-
По завершении игры выводятся результаты с указанием общей суммы выигрыша/проигрыша в кредитах для каждого игрока
Пример игры:
current_game = DiceGame.create user_1, user_2
user_1.bet score: 12, credits: 400
user_2.bet score: 7, credits: 350
current_game.turn
# => Wheel of fortune throws: 7
# => user_1 loses
# => user_2 wins
user_1.bet score: 6, credits: 100
user_2.bet score: 7, credits: 300
current_game.turn
# => Wheel of fortune throws: 6
# => user_1 wins
# => user_2 loses
current_game.finish
# => Game results:
# => user_1 earns -375 credits
# => user_2 earns -212.5 credits
Вы можете использовать любую структуру каталогов, классы и модули. Один из вариантов:
-
User
класс пользователя -
Dice
класс игральной кости -
DiceGame
модуль всего приложения -
Validations
модуль валидаций
- GeekBrains
- Электронная почта:
[email protected]
- Slack канал