Skip to content

Instantly share code, notes, and snippets.

@psylone
Last active September 14, 2016 01:26
Show Gist options
  • Save psylone/9c897e71cc5e70ae3f75 to your computer and use it in GitHub Desktop.
Save psylone/9c897e71cc5e70ae3f75 to your computer and use it in GitHub Desktop.
Задания к занятию 5

Задания к занятию 5

  • Механизм валидации данных
  • Модули
  • Методы класса/модуля
  • Структура каталогов 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

Теперь всё в порядке.

1. Модуль определения возраста

Напишите модуль 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. Игра в «Кости»

Напишите приложение для игры в «Кости». Основные правила игры:

  • По умолчанию в игре используется 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 модуль валидаций

Контакты для связи

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment