Архив для  Июль 2017

Главная / Июль 2017
11 Постов

В этом посте мы рассмотрим:

  • Что такое хелперы и чем они полезны
  • Зачем использовать Rails-хелпер link_to

Хелперы

Чтобы понять концепцию хелперов, давайте взглянем на переменную текущего времени, которая была определена в нашем контроллере (что хорошо).

Что если мы хотим форматировать то, как выглядит это время?

Должен ли этот код быть во View? Вряд ли, в этом случае он не будет многоразовым, то есть его придется определять каждый раз.

Должен ли он быть в контроллере? Вряд ли, потому что контроллер должен быть независим от формата представления.

Так что, здесь приходят на помощь хелперы.

Каждый раз, когда вы генерируете контроллер, также генерируется (пустой) хелпер.

Давайте добавим в него метод.

Интересно то, что метод внутри хелпера будет доступен для всех view в вашем приложении.

Не забываем указать formatted_time(@time) во view.

И это работает, на странице показывается отформатированное время.

Встроенные в Rails хелперы: link_to

Rails предоставляет много встроенных хелперов.

Один из них, это хелпер link_to.

link_to name, path генерирует гиперссылку, которая отображает имя (name) и ссылается на путь (path).

В вашем HTML вы будете иметь тег <a>.

Путь может быть обычной строкой или маршрутом указанным в файле routes.rb, и оканчиваться на _url (полный путь) или _path (относительный путь).

Это полезно потому, что вы можете указать переменную, и ваша страница изменится автоматически, если изменится эта переменная.

link_to в действии

Добавим пару ссылок в наш view.

Первый параметр это имя.

В первом примере мы ссылаемся на Google и определяем полный URL, во втором — используем внутренний путь, определенный в routes.rb.

Конечно, это будет работать без проблем.

В итоге

Хелперы являются «макросами» для вашего Представления.

При использовании link_to, нет необходимости что-то менять, когда меняется путь.
Поэтому он может быть удобнее простого HTML-тега.

В этом посте мы рассмотрим то, как перенести бизнес-логику из Представления в Контроллер в соответствии с MVC-паттерном.

Также мы поговорим о том, как долго переменные экземпляров контроллера остаются на месте.

Действия (методы) внутри контроллера

Если действие (метод) ничего не делает, мы можем просто убрать его.

Пока определен правильный маршрут и есть правильно названный файл/шаблон представления, действие не обязано присутствовать, и Rails найдет правильный шаблон при помощи конвенции.

Контроллер: новый вид

Так что, мы можем закомментировать (или просто удалить) действия в нашем контроллере, и он всё еще будет работать.

Но, конечно же, есть причина иметь действия внутри контроллера, они не просто для декорации.

Переносим бизнес-логику

Наше приложение работает, но бизнес-логика не должна находиться во View. Представление должно иметь настолько мало Ruby-кода, насколько это возможно.

Давайте перенесем эту бизнес-логику из Представления в Контроллер.

Переменные экземпляров из контроллера доступны внутри view.

Попробуем также добавить счетчик просмотров. Изначально, @times_displayed будет равняться 0, и с каждым просмотром страницы это число должно увеличиваться на 1.

Здесь мы просто используем переменные из контроллера.

Посмотрим что получилось.

В обоих случаях просмотрено 1 раз? Интересно.

Переменные экземпляров в Rails

Дело в том, что, в отличие от других веб-фреймворков (к примеру, Servlets), вы не можете хранить значения в переменных контроллера между запросами.

Таким образом, каждый новый запрос создает новую переменную экземпляра.

Какие альтернативы? Можно хранить их в HTTP-сессии, либо в базе данных.

В итоге

Старайтесь держать бизнес-логику вне View.

Переменные экземпляров в контроллере доступны для View.

Переменные экземпляров не задерживаются между запросами.

В этом посте мы рассмотрим как работает маршрутизация, что такое Rake и как анализировать ваши маршруты.

Маршруты

В предыдущем посте мы пытались добавить действие «goodbay» в наш контроллер, но это не сработало, потому что не хватало еще одной важной части. Этой частью является маршрутизация.

Перед тем как контроллер может скоординировать куда идет веб-запрос, этот запрос должен быть направлен к контроллеру.

Так как работало действие «hello»?

Маршрут к нему был автоматически сгенерирован, когда мы выполняли rails g controller.

Давайте дополним цикл MVC из одного из прошлых постов.

  1. Отправлен запрос
  2. Маршрутизатор направляет запрос к контроллеру
  3. Контроллер ← → Модель
  4. Контроллер запускает Представление
  5. Представление отображает данные

routes.rb

Все маршруты должны быть указаны (вручную или с помощью генератора) в файле config/routes.rb.

Как этой файл выглядит?

Изначально в нём много комментариев.

Запись get 'greeter/hello' означает, что когда вы отправляете get-запрос, к примеру в браузере, он должен быть интерпретирован как «контроллер greeter, действие hello».

Давайте добавим маршрут для действия goodbay.

Мы можем указать просто get 'greeter/hello' (или 'greeter/goodbay'), или мы можем указать это в стиле get 'greeter/hello' => "greeter#hello", что означает, что этот запрос направляется к «контроллер greeter, действие hello».

В данном примере они не имеют разницы, но если бы вы хотели указать другой адрес слева, вы бы использовали именно второй способ.

Итак, наш маршрут для goodbay работает.

Rake

Rake это язык сборки Ruby. Его имя происходит от «Ruby’s make».

В отличие, к примеру, от Ant в Java, он не написан на XML, он написан только на Ruby.

Rails использует Rake для автоматизации связанных с приложением заданий, к примеру операций с базой данных, тестами и т.д.

Чтобы увидеть список всех rake-заданий для вашего приложения, можно выполнить rake --tasks.

Также можно запросить описание индивидуального задания с помощью rake --describe.

rake routes показывает все ваши указанные маршруты.

В итоге

Маршрут направляет запрос к правильному контроллеру.

rake routes позволяет увидеть какие маршруты на данный момент определены в вашем приложении.

В этом посте мы затронем создание динамичного контента. Конкретно мы рассмотрим:

  • Как создать контроллер
  • Действия
  • Встроенный Ruby (ERB)

Создание контроллера

Контроллер содержит действия (методы Ruby) и координирует веб-запросы.

Rails может быстро сгенерировать контроллер с 0 или более действий с их ассоциированными представлениями.

Генерация происходит следующим образом:

rails generate controller controller_name [action1 action2]

Команда generate может быть сокращена до g.

Попробуем сгенерировать контроллер greeater, «hello» будет именем действия и именем представления.

Таким образом, мы получаем контроллер greeter.rb в папке app/controllers. Также получаем файл app/views/greeter/hello.html.erb.

Если вы заглянете в этот файл представления, вы увидите, что там нет типичных для HTML элементов Doctype, head или body. Мы увидим немного позже, почему.

Как это выглядит?

Встроенный Ruby (ERB)

Сгенерированный файл представления выглядит как типичный HTML-файл, но он также имеет расширение .erb.

ERB это библиотека шаблонизации (похожая на JSP, к примеру), которая позволяет вам встраивать Ruby в ваш HTML.

Стоит выучить два паттерна:

  • <% ...код ruby... %> — рассчитывает код
  • <%= ...код ruby... %> — возвращает рассчитанный код

И еще раз, идея ERB в том, чтобы иметь возможность включать динамичный контент в статичный HTML-контент, чтобы смешивать их вместе.

Так что, к примеру, мы можем переписать нашу приветственную страницу.

В этом случае в первой строке мы создаем переменную, которая содержит массив имен. Затем мы выводим его с помощью второго паттерна, который отличается знаком равенства в начале.

Теперь при обновлении страницы, она показывает случайное имя из списка.

Как выглядит контроллер

Итак, это была часть про представление. Как же выглядит сам контроллер?

hello это просто действие. Это обычный метод Ruby, в данном случае пустой.

И это работает.

А что если мы хотим добавить действие «goodbay» в этот контроллер, и также goodbay.html.erb в папку app/views/greeter?

Давайте попробуем.

Создаем файл для view в соответствующей папке, затем редактируем контроллер.

Заходим на страницу /greeter/goodbay:

Не сработало. Так как в этом случае не хватает компонента, который мы собираемся рассмотреть в следующем посте.

В итоге

Контроллеры содержат действия (методы).

ERB позволяет рассчитывать выражения с помощью паттерна <% expression %> или выводить их с помощью <%= expression %>.

В этом посте я расскажу о том как создать и запустить ваше первое приложение, о структуре каталогов и о добавлении статических страниц.

Чтобы создать свое первое приложение, перейдите в каталог, в котором хотите его создать. Введите rails new my_first_app, где «my_first_app» — имя приложения.

Это создаст определенную структуру, о которой я расскажу немного ниже.

Вы также можете выполнить rails new -h, в этом случае вы увидите какие параметры вы могли бы указать при создании нового Rails-приложения.

Bundler (менеджер gem’ов)

Ближе к концу создания приложения вы увидите «bundle install». Его идея заключается в том, что в приложении у вас имеется множество разных компонентов, которые объединяются вместе и имеют разные версии, и, очевидно, они должны взаимодействовать.

Один компонент может зависеть от одной версии, другой компонент от другой. И вам нужно разрешить эти зависимости, — это то что делает Bundler.

Контроль версий вашего приложения

Итак, сейчас ваше приложение создано. Rails сгенерировал изначальный скелет и даже немного кода.

Также интересно, что Rails автоматически создает файл .gitignore, что показывает, что он подталкивает вас к использованию Git.

Создаем репозиторий и делаем наш первый коммит:

cd my_first_app
git init
git add .
git commit -m "Initial commit"

Запускаем приложение

Кроме создания скелета приложения, Rails также имеет встроенный веб-сервер.

Так что, чтобы запустить приложение, вам не нужно ничего скачивать или ставить костыли.

Опционально открываем новое окно терминала, переходим в нужную директорию (cd my_first_app) и выполняем:

rails server

Либо rails s, что является той же командой в сокращенном виде.

Здесь говорится о версии Rails, о режиме в котором вы находитесь (development, т.е. разработки) и адресе по которому запущено приложение (http://localhost:3000).

Это веб-сервер на вашем компьютере, а порт 3000 является стандартным портом. Вы можете его изменить, но по умолчанию это 3000.

Когда вы развернете ваше приложение, к примеру на Heroku, оно будет работать в режиме production.

Открываем в браузере localhost:3000, и Rails уже приветствует нас.

Rails дает советы о том что делать дальше, к примеру использовать генераторы или настроить базу данных. Справа приводятся полезные ссылки.

Конвенция структуры директорий

Итак, приложение будет иметь директорию app, на которую вы будете тратить большую часть времени. Она включает модели, контроллеры и представления (папки controllers, models и views), а также хелперы и прочее.

Следующая интересная директория это config, она включает различные конфигурации, к примеру какую БД вы собираетесь использовать и пароль от нее.

Папка db будет иметь скрипты миграций вашей базы данных, а также сам файл БД если вы используете SQLite (которая используется по умолчанию).

Папка public содержит статичные файлы. К примеру, HTML-файлы, которые вы редко будете изменять, пойдут в эту папку.

Gemfile и Gemfile.lock используются Bundler’ом для разрешения зависимостей. Здесь вы указываете нужные gem’ы.

public / hello_static.html

Мы упоминали, что статичные файлы идут в папку public.

Сервер проверяет папку public перед тем как проверить всё остальное. Если файл найден в этой директории, он просто отображается по ссылке.

Так что, если вы хотите добавить полностью статичную страницу, вы добавляете её в эту папку.

Попробуем добавить такой файл.

Переходим на localhost:3000/hello_static.html. Если ваш сервер уже запущен, нет нужды перезапускать его.

В большинстве случаев, конечно, вы захотите создавать динамические страницы, которые, к примеру, будут использовать данные из базы данных или из других мест.

То есть, Rails дает возможность добавлять статические страницы, но это не та технология и не тот фреймворк, если вы хотите иметь только лишь их.

В итоге

rails new создает новое приложение.

rails server / rails s запускает его (нужно выполнять в папке с самим приложением).

Статичные страницы находятся в папке public.

Мы рассмотрели Ruby, теперь можно переходить к Ruby on Rails. В этом и следующих постах мы будем обсуждать основные принципы, конвенции и MVC-структуру.

Итак, в этом посте я расскажу о следующих вещах:

  • Краткая история Rails
  • Польза от использования Rails
  • Model View Controller

История Ruby on Rails

Rails (RoR) — это фреймворк для разработки динамичных веб-приложений.

Он был создан в 2004-2005 годах Давидом Ханссоном (который, кроме того, является автогонщиком).

Кто использует Rails?

Так кто использует Rails? Много больших, а также средних сайтов, таких как Hulu, Groupon, LivingSocial, Twitter и Github.

Стартапы в целом любят Rails из-за его возможности быстро прототипировать и быстро разрабатывать приложения.

Почему Rails?

Один из принципов использования Rails называется Convention Over Configuration.

Вы бы могли подойти к разработке приложений по одному из двух путей. В одном из них вы настраиваете всё что вы делаете с чистого листа.

Или вы можете положиться на конвенции, которые уже были установлены. Многие лучшие практики уже существуют в конвенциях.

И если вы следуете определенным конвенциям, вам нужно писать меньше кода — иногда фреймворк генерирует его за вас.

Другим преимуществом этого паттерна является то, что вы изучаете разработку проекта один раз, и вы можете применить те же самые концепты в следующем Rails-проекте.

Так что, вы будете знать чего ожидать в следующий раз.

Следующее преимущество Rails — это то что у вас есть уровень абстракции базы данных. Ruby on Rails позволяет вам взаимодействовать с базой данных не путем написания SQL-запросов, вы можете взаимодействовать с базой данных путем написания Ruby-кода.

Это иногда называется ORM, то есть объектно-реляционное сопоставление.

Вы сопоставляете свою базу данных с вашими классами Ruby.

Кроме того, Rails:

  • Благоприятен для гибкой разработки
  • Следует принципу «не повторяйся» (Don’t repeat yourself)
  • Кроссплатформенный
  • С открытым исходным кодом
  • Модульный

SQLite

Другой интересной особенностью Rails является то, что он использует SQLite как базу данных по умолчанию.

SQLite — это автономная, безсерверная, транзакционная база данных SQL нулевой конфигурации.

Что в основном означает, что это всего лишь файл, лежащий в вашей файловой системе, но он действует как реляционная база данных.

Преимущество этого заключается в том, что вам не нужно настраивать сложный механизм базы данных, чтобы начать разработку приложения.

Когда вы создаете Rails-приложение, возможность взаимодействия с базой данных находится прямо «в коробке».

Поскольку база данных здесь абстрактна, вы можете переключиться на другую СУБД позже, если захотите. Но в самом начале вам не нужно проходить эти детали настройки.

Кроме того, SQLite является наиболее широко распространенной SQL-базой в мире. Ее используют браузеры, iPhone, Android и многие большие приложения. Потому что, опять же, она ведет себя как реляционная база данных, но в реальности это всего лишь файл.

MVC: Module, View, Controller

Еще одна концепция, которую использует Rails, — это идея Model View Controller. На русский это переводится как «Модель-Вид-Контроллер» или «Модель-Представление-Контроллер».

MVC изобрел норвержский профессор Трюгве Реенскауг в 1979 году.

Это хорошо зарекомендовавший себя программный паттерн, используемый многими веб-фреймворками и десктопными фреймворками.

Model — представляет собой данные, с которыми работает приложение (и, возможно, бизнес-логику).

View — визуальное представление этих данных.

Controller — координирует взаимодействие между моделью и представлением.

Цикл MVC

  1. Отправлен запрос
  2. Контроллер ← → Модель
  3. Контроллер запускает Представление
  4. Представление отображает данные

MVC в Rails

В итоге

В итоге, Rails очень хорошо подходит для быстрого прототипирования.

Использование паттернов и конвенций позволяет меньше думать и больше делать, таким образом увеличивая продуктивность разработчика.

В этом посте мы продолжим разбирать RSpec и поговорим о matchers.

«Matchers» не переводится с английского, но слово match в этом контексте означает «совпадать, равняться».

Итак.

RSpec «вешает» методы to и not_to на все результаты ожиданий.

Эти два метода принимают один параметр — matcher.

Примеры matcher’ов:

  • be_true / be_false
  • eq 3
  • raise_error(SomeError)

Be_predicate — логическое значение

Если объект, на котором проводится тест, имеет утвердительный (логический) метод, вы автоматически получите матчер be_predicate.

Так что, к примеру, be_nil является валидным матчером, так как каждый объект в Ruby имеет метод :nil?.

В нашем примере с калькулятором мы можем иметь такой тест:


it "should sum two odd numbers and become even" do
 expect(@calculator.add(3, 3)).to be_even
 expect(@calculator.add(3, 3)).not_to be_odd
end

Это полностью валидно и выглядит почти как естественный английский.

Так как мы исправили ошибки в калькуляторе, все тесты работают.

Но если вы хотите узнать подробнее, что работает, а что нет, введите rspec --format documentation или rspec -f d:

Больше матчеров

Полный список матчеров можно найти в документации RSpec.

https://relishapp.com/rspec/rspec-expectations/v/3-6/docs/built-in-matchers

В итоге

RSpec имеет множество встроенных матчеров, которые доступны для упрощения написания тестов, я думаю это отличный фреймворк для тестирования приложений.

Хотя если вы предпочитаете MiniTest, используйте его.

В этом посте мы продолжим говорить о юнит-тестировании в Ruby и рассмотрим RSpec.

Тестирование с помощью RSpec

Test::Unit делает свою работу, но было бы неплохо, если бы тесты были более описательными, более похожими на естественный английский.

RSpec позволяет это.

Так что, здесь написание тестов более интуитивно, как и вывод после выполнения тестов.

Установка RSpec

Устанавливаем RSpec командой gem install rspec.

describe()

Метод describe() это набор связанных тестов.

Он принимает строку либо класс в качестве аргумента.

Все тесты должны быть внутри этого блока.

Здесь нет подкласса для класcа (в отличие от Test::Unit), всё происходит внутри метода describe().

Методы before() и after()

Методы before() и after() похожи на setup() и teardown() в MiniTest.

Они принимают значения :each или :all, чтобы указать будет блок выполняться перед/после каждого теста или один раз перед/после всех тестов.

К примеру, before :all был бы полезен, если вы хотите подключиться к базе данных лишь единожды.

Метод it()

Используется для определения фактических спецификаций/примеров RSpec.

Принимает опциональную строку, описывающую тестируемое поведение.

Пример

Давайте возьмем для примера тот же код калькулятора, который был в предыдущей статье, но в этот раз протестируем его с помощью RSpec.


class Calculator

  attr_reader :name

  def initialize(name)
  	@name = name
  end

  def add(one, two)
  	one - two # скажем, вы допустили ошибку здесь (- вместо +)
  end

  def subtract(one, two)
  	one + two # и в этом методе
  end

  def divide(one, two)
  	one / two
  end
end

Выполним в терминале команду rspec --init в нужной папке, чтобы всё было подготовлено.

spec/calculator_spec.rb:


require 'rspec'
require_relative '../calculator' # или calculator.rb, без разницы

describe Calculator do # здесь вместо класса Calculator можно быть бы написать строку, но здесь это не имеет смысла
  before { @calculator = Calculator.new('RSpec calculator')}

# Фактические спецификации:

  it "should add 2 numbers correctly" do
  	expect(@calculator.add(2, 2)).to eq 4 
  end
  	
  it "should subtract 2 numbers correctly" do
   expect(@calculator.subtract(4, 2)).to eq 2 
  end  

  it "should sum two odd numbers and become even" do
   expect(@calculator.add(3, 3)).to be_even 
   expect(@calculator.add(3, 3)).not_to be_odd 
  end

end

Как мы видим, RSpec более интуитивный и больше похож на естественный английский.

Выполняем команду rspec.

После устранения ошибок результат выполнения теста будет выглядеть так:

В итоге, если вам нравится MiniTest, можете использовать его. Если вам нравится выразительность RSpec, попробуйте это.

Много раз вам придется писать тесты. Некоторые делают это после написания основного кода, некоторые после. Но в целом это довольно важная часть.

В этом посте мы рассмотрим юнит-тестирование (модульное тестирование).

Идея юнит-тестирования заключается в написании тестового кода, который проверяет ваш живой код.

Так почему люди пишут юнит-тесты?

Как вы узнаете, работает ли ваш код? Вы фактически понятия не имеете, пока не запустите его и не получите ожидаемые результаты.

Это особенно верно для Ruby, который является динамическим языком. В языке статического типа, таком как Java, если вы делаете опечатку, ваш редактор, вероятно, скажет вам об этом.

Еще одна причина для написания модульных тестов — когда вы хотите отрефакторить ваш код и быть уверенным, что вы ничего не сломали.

Для этого нужно добавить юнит-тесты. Когда у вас есть юнит-тесты, которые выполняются успешно, вы можете знать, что все работает как надо.

Другая причина для написания тестов заключается в том, что они могут послужить документацией для других разработчиков.

Test::Unit

Ruby очень серьезно относится к тестированию и предоставляет для этого фреймворк Test::Unit.

В Ruby 1.8 Unit::Test был загружен лишними библиотеками, которые включали много ненужного кода.

Ruby 1.9 уменьшил Test::Unit до необходимого минимума.

Новый фреймворк был официально назван MiniTest, хотя на него можно по-прежнему ссылаться под старым названием.

Обзор Test::Unit

  • Test::Unit входит в семью XUnit (наряду с JUnit, CppUnit)
  • Основная идея — расширение Test::Unit::TestCase
  • Перед именем метода приставляется test_
  • Если один из методов не удается, другие продолжают проверяться (это хорошая вещь)
  • Можно использовать методы setup() и teardown() для установки поведения, которое будет выполняться перед каждым тест-методом

Давайте посмотрим на реальный пример. Скажем, вы пишите калькулятор.


class Calculator

  attr_reader :name

  def initialize(name)
  	@name = name
  end

  def add(one, two)
  	one - two # скажем, вы допустили ошибку здесь (- вместо +)
  end

  def subtract(one, two)
  	one + two # и в этом методе
  end

  def divide(one, two)
  	one / two
  end
end

Тестируем:


require 'test/unit'
require_relative 'calculator.rb'

class CalculatorTest < Test::Unit::TestCase

  def setup
  	@calc = Calculator.new('test')
  end

  def test_addition
    assert_equal 4, @calc.add(2,2)
  end

  def test_subtraction
    assert_equal 2, @calc.subtract(4,2)
  end

  def test_division
    assert_equal 2, @calc.divide(4,2)
  end

end

Когда вы выполните тест, вы увидите череду ошибок:

Если исправить наши ошибки, тест будет полностью доволен.

Также можно попробовать протестировать деление на ноль, в этом случае мы ожидаем, что наша программа должна выдать исключение.


require 'test/unit'
require_relative 'calculator.rb'

class CalculatorTest < Test::Unit::TestCase
  def setup
  	@calc = Calculator.new('test')
  end

  def test_divide_by_zero
    assert_raise ZeroDivisionError do 
      @calc.divide(1, 0)
    end
  end
end

С этим наш калькулятор должен справиться, так что этот тест покажет успех.

Классы

Для начала мы поговорим о классах. Мы рассмотрим:

  • Классы
  • Как создаются объекты
  • Как получить доступ к данным в них

Обзор объектно-ориентированного программирования

  • Определите вещи, с которыми ваша программа имеет дело
  • Этими вещами (их «чертежами») являются классы
    • Классы содержат методы (поведение)
  • Объекты являются экземплярами этих вещей
  • Объекты содержат переменные экземпляров (состояние)

Переменные экземпляров

В Ruby, переменные экземпляров начинаются на @. К примеру, @name.

Интересно то, что в Ruby переменные экземпляра не объявляются. Они как бы просто оживают, когда вызваны впервые.

Они доступны для всех методов экземпляра класса.

Создание объекта

Способ создания объектов в Ruby — вызов метода new в классе, фактически это вызывает метод initialize.

Метод initialize в Ruby — это специальный метод, который действует как конструктор, как мы увидим в примере ниже.

Состояние объекта должно быть инициализировано внутри метода initialize, то есть конструктора.


class Person
  def initialize (name, age) # "Конструктор"
    @name = name
    @age = age
  end
  def get_info
    @additional_info = "Интересно"
    "Имя: #{@name}, возраст: #{@age}"
  end
end

person1 = Person.new("Джо", 14)
p person1.instance_variables # [:@name, :@age]
puts person1.get_info # => Имя: Джо, возраст: 14
p person1.instance_variables # [:@name, :@age, :@additional_info]

Доступ к данным

Переменные экземпляров являются приватными, они не могут быть доступны вне класса.

Методы имеют публичный доступ по умолчанию.

Для доступа к переменным экземпляров нужно определить методы «getter» / «setter».


class Person 
  def initialize (name, age) # Конструктор
    @name = name 
    @age = age 
  end 
  def name # name getter
    @name 
  end 
  def name= (new_name) # name setter
    @name = new_name 
  end 
end 

person1 = Person.new("Джо", 14) 
puts person1.name # Джо
person1.name = "Майк" 
puts person1.name # Майк
# puts person1.age # undefined method `age' for #<Person:

Часто логика getter/setter довольно проста: получить существующее значение / установить новое значение.

Но должен быть путь еще проще, чем фактическое определение методов getter/setter.

Так что можно использовать синтаксис attr_*:

  • attr_accessor — getter и setter
  • attr_reader — только getter
  • attr_writer — только setter

class Person 
  attr_accessor :name, :age # геттеры и сеттеры для name и age
end 

person1 = Person.new 
p person1.name # => nil 
person1.name = "Майк" 
person1.age = 15 
puts person1.name # => Майк 
puts person1.age # => 15 
person1.age = "пятнадцать" 
puts person1.age # => пятнадцать

В этом примере есть две небольшие проблемы:

  • При создании, Person находится в неинициализированном состоянии (без name или age)
  • Скорее всего, мы хотели бы контролировать максимальный установленный возраст

Чтобы решить это, нужно использовать конструктор и более разумный setter для age.

Но для начала мы должны рассмотреть self.

self

Внутри метода экземпляра, self относится к самому объекту.

Обычно, использование self для вызова других методов того же экземпляра является чуждым.

Но иногда вызов self является обязательным, к примеру когда он может означать назначение локально переменной.

Вне определения метода экземпляра, self относится к самому классу.


class Person 
  attr_reader :age 
  attr_accessor :name 

  def initialize (name, ageVar) # Конструктор
    @name = name 
    self.age = ageVar # вызываем age= метод
    puts age 
  end 
  def age= (new_age) # более разумный setter для age
    @age = new_age unless new_age > 120
  end 
end 

person1 = Person.new("Ким", 13) # => 13 
puts "Мой возраст #{person1.age}" # => Мой возраст 13 
person1.age = 130 # Попытка изменить возраст
puts person1.age # => 13 (setter не дал провести изменение)

Далее мы поговорим о методах/переменных и наследовании классов.

Наследование классов

Мы рассмотрим:

  • Оператор ||
  • Методы классов и операторы классов
  • Наследование классов

Оператор ||

Оператор || оценивает левую сторону.

  • Если true — возвращает ее
  • В другом случае, возвращает правую сторону
  • @x = @x || 5 в первый раз возвратит 5, в следующий — @x

Краткая форма:

  • @x ||= 5 — то же, что и сверху

var = var || что-то

Давайте посмотрим чем это может быть полезно.


class Person 
  attr_reader :age 
  attr_accessor :name 
  
  def initialize (name, age) # Конструктор
    @name = name 
    self.age = age # вызывает age= метод
  end 
  def age= (new_age) 
    @age ||= 5 # устанавливаем 5 только для первого раза
    @age = new_age unless new_age > 120 
  end 
end 
person1 = Person.new("Ким", 130) 
puts person1.age # => 5 (по умолчанию) 
person1.age = 10 # изменяем на 10 
puts person1.age # => 10 
person1.age = 200 # Пытаемся изменить на 200
puts person1.age # => 10 (всё еще)

Методы и переменные классов

Давайте поговорим методах классов и переменных классов.

Они вызываются на классе, а не экземпляре класса. Так что если вы знакомы с концептом метода static в Java, это его эквивалент.

Self вне определения метода относится к объекту Class.

Как мы увидим, есть еще два способа определить методы класса в Ruby, кроме self вне определения метода.

Переменные класса начинаются на @@.

Единичный символ @ дал бы вам переменную экземпляра, как уже упоминалось, а двойной символ даст переменную класса.


class MathFunctions 
  def self.double(var) # 1. Используем self 
    times_called; var * 2; 
  end 
  class << self # 2. Используем << self 
    def times_called 
      @@times_called ||= 0; @@times_called += 1 
    end 
  end 
end 
def MathFunctions.triple(var) # 3. Вне класса
  times_called; var * 3 
end

# Без создания экземпляра!
puts MathFunctions.double 5 # => 10 
puts MathFunctions.triple(3) # => 9 
puts MathFunctions.times_called # => 3 

Наследование класса

Каждый класс неявно наследуется от Object. Сам Object наследуется от BasicObject.

Множественного наследования нет, вместо этого используются примеси (mixins).

Давайте взглянем на небольшой пример.


class Dog # неявно наследует Object
  def to_s
    "Собака"
  end
  def bark
    "лает громко"
  end
end
class SmallDog < Dog # означает наследование, наследует класс Dog
  def bark # Перезаписываем
    "лает тихо"
  end
end

dog = Dog.new # (кстати, new это метод класса)
small_dog = SmallDog.new
puts "#{dog}1 #{dog.bark}" # => Собака1 лает громко
puts "#{small_dog}2 #{small_dog.bark}" # => Собака2 лает тихо

Модули

Теперь рассмотрим модули.

  • Модули
    • Как пространства имен
    • Как примеси
  • Использование встроенных в Ruby модулей, особенно Enumerable
  • require_relative

В общем-то, модули являются контейнерами для классов, методов и констант. Или других модулей.

Они походи на Классы, но не могут быть инстанцированы.
Class наследует Module и добавляет new.

Модули полезны для двух целей: пространства имен и примеси.

Модуль как пространство имен


module Sports
  class Match # Класс match как часть модуля Sports
    attr_accessor :score
  end
end

module Patterns
  class Match # Класс match как часть модуля Patterns
    attr_accessor :complete
  end
end

# Итак, мы имеем два разных Match

match1 = Sports::Match.new # заметьте использование оператора ::
match1.score = 40; puts match1.score # => 40

match2 = Patterns::Match.new
match2.complete = true; puts match2.complete # => true

Модуль как примеси

Другой способ использования модулей — это примеси.

В ОО-программировании есть интерфейсы. Они могут определять что класс «может» делать.

Примеси идут немного дальше и позволяют разделять (примешивать) готовый код между несколькими классами.

Кроме того, вы можете включить встроенные модули как Enumerable, которые могут сделать трудную работу за вас.


module SayMyName
  attr_accessor :name
  def print_name
    puts "Имя: #{@name}"
  end
end

class Person
  include SayMyName # позволяет включить функциональность этого модуля
end
class Company
  include SayMyName
end

person = Person.new
person.name = "Джо"
person.print_name # => Имя: Джо
company = Company.new
company.name = "Google & Microsoft LLC"
company.print_name # => Имя: Google & Microsoft LLC

Модуль Enumerable

Что действительно интересно, вы можете включить встроенные модули из самого Ruby. Когда мы говорили о массивах, мы затрагивали, что они имеют такие методы как map, select, reject, detect и т.д.

Дело в том, что массивы включают модуль Enumerable и это то что дает им такую функциональность.

Вы можете включить его в свой собственный класс.

Все что вам для этого нужно — обеспечить реализацию метода each. Как только вы это сделаете, вся функциональность Enumerable будет доступна!

Давайте взглянем на пример.
Скажем, у вас есть команда. Вы можете записать несколько классов в одном файле или сделать несколько файлов — оба метода полностью валидны.


# имя файла - player.rb

class Player

  attr_reader :name, :age, :skill_level

  def initialize (name, age, skill_level)
    @name = name
    @age = age
    @skill_level = skill_level
  end

  def to_s
    "<#{name}: #{skill_level}(SL), #{age}(возраст)>"
  end

end


# team.rb

class Team
  include Enumerable # Множество функций

  attr_accessor :name, :players
  def initialize (name)
    @name = name
    @players = []
  end
  def add_players (*players) # splat
    @players += players
  end
  def to_s
    "#{@name} team: #{@players.join(", ")}"
  end
  def each
    @players.each { |player| yield player }
  end
end


require_relative 'player' # require_relative позволяет включать другие .rb файлы
require_relative 'team'

player1 = Player.new("Боб", 13, 5); player2 = Player.new("Джим", 15, 4.5)
player3 = Player.new("Майк", 21, 5) ; player4 = Player.new("Джо", 14, 5)
player5 = Player.new("Скотт", 16, 3)

red_team = Team.new("Красная команда")
red_team.add_players(player1, player2, player3, player4, player5) # (splat)

# выбирает только игроков от 14 до 20 and отклоняет всех ниже уровня SL 4.5
elig_players = red_team.select {|player| (14..20) === player.age }
                       .reject {|player| player.skill_level < 4.5}
puts elig_players # => <Джим: 4.5(SL), 15(возраст)>
		  # => <Джо: 5(SL), 14(возраст)>

Области

Теперь рассмотрим области.

  • Области переменных
  • Области констант
  • Как область работает с блоками

Методы и классы начинают новую область для переменных.

Внешние переменные не имеют отношения к внутренней области.

Всегда можно использовать метод local_variables, чтобы посмотреть какие переменные входят (а какие — нет) в текущую область.


v1 = "outside" # вне области

class MyClass 
  def my_method
  	# p v1 Выдается исключение - нет такой переменной
    v1 = "inside" # внутри
    p v1
    p local_variables 
  end 
end 

p v1 # => outside
obj = MyClass.new 
obj.my_method # => inside 
              # => [:v1] 
p local_variables # => [:v1, :obj]
p self # => main

Область: константы

  1. Константа — это любая ссылка, которая начинается с заглавной буквы, включая классы и модули.
  2. Правила области константы отличаются от правил области переменных.
  3. Внутренняя область может использовать константы, определенные вне этой области, и также может перезаписывать внешние константы.
    • При этом внешнее значение остается неизменным за ее пределами

Давайте взглянем на пример.


module Test
  PI = 3.14
  class Test2
    def what_is_pi
      puts PI
    end
  end
end
Test::Test2.new.what_is_pi # => 3.14

module MyModule
  MyConstant = 'Внешняя константа'
  class MyClass
    puts MyConstant # => Внешняя константа
    MyConstant = 'Внутренняя константа'
    puts MyConstant # => Внутренняя константа
  end

  # остается неизменной вне области
  puts MyConstant # => Внешняя константа
end

Область: блоки

Блоки, в отличие от методов, наследуют внешнюю область.

Блоки закрыты. Они запоминают контекст в котором они были определены и используют этот контекст при каждом вызове.

Посмотрим на пример для блоков.


class BankAccount
  attr_accessor :id, :amount
  def initialize(id, amount)
    @id = id
    @amount = amount
  end
end

acct1 = BankAccount.new(123, 200) # id, сумма
acct2 = BankAccount.new(321, 100)
acct3 = BankAccount.new(421, -100)
accts = [acct1, acct2, acct3]

total_sum = 0 # объявляем переменную
accts.each do |eachAcct| # считаем общую сумму в блоке
  total_sum += eachAcct.amount
end

# та же область
puts total_sum # => 200

Блок – локальная область

Даже хотя блоки разделяют внешнюю область, переменная созданная внутри блока доступна только этому блоку.

Параметры блока всегда локальны для него, даже если они имеют то же имя, что и переменные внешней области.

Вы можете явно указывать локальные для блока переменные после двоеточия в списке параметров блока.


arr = [5, 4, 1] 
cur_number = 10 
arr.each do |cur_number| 
  some_var = 10 # Не доступна вне блока
  print cur_number.to_s + " " # => 5 4 1 
end 
puts # отдает пустую строку
puts cur_number # => 10 

adjustment = 5 
arr.each do |cur_number;adjustment| 
  adjustment = 10 
  print "#{cur_number + adjustment} " # => 15 14 11 
end 
puts 
puts adjustment # => 5 (Не затронута блоком)

Контроль доступа

Теперь рассмотрим:

  • Три уровня контроля доступа
  • Управление доступом
  • Насколько приватен приватный доступ?

При проектировании класса, важно подумать о том как много из него будет раскрыто миру.

Инкапсуляция: старайтесь спрятать внутреннее представление объекта, чтобы позже вы могли изменить его.

Всего Ruby предоставляет три метода контроля доступа: public, protected и private.

Инкапсуляция

В примере ниже детали рассчета рейтинга сохраняются внутри класса.


class Car
  def initialize(speed, comfort)
    @rating = speed * comfort
  end

  # Нельзя задать rating извне
  def rating
    @rating
  end
end

puts Car.new(4, 5).rating # => 20

Назначение контроля доступа

В Ruby есть два пути назначения контроля доступа:

  1. Указать public, protected или private
    • Всё до следующего ключевого слова управления доступом будет этого уровня доступа
  2. Определить методы обычным методом и затем указать public, protected или private, и перечислить через запятую методы под этими уровнями, используя символы методов

По умолчанию (если вы не указываете ни один из уровней), методы являются public.

Итак, взглянем на пример такого назначения.


class MyAlgorithm
  private
    def test1
      "Приватный"
    end
  protected
    def test2
      "Защищенный"
    end
  public
    def public_again
      "Публичный"
    end
end

# Второй способ:

class Another
  def test1
    "Приватный, как объявлено ниже"
  end
  private :test1
end

Что означают публичный, защищенный и приватный уровни?

public методы — нет установленного управления доступом. Кто угодно может вызывать эти методы.

protected методы — могут быть вызваны объектами определяющего класса или его подклассами.

private методы — не могут быть вызваны с явным получателем.
Исключение: установка атрибута может быть вызвана с явным получателем.

Приватный доступ


class Person
  def initialize(age)
  	self.age = age # Валидно - исключение
  	puts my_age
  	# puts self.my_age # Не валидно
  	                   # Нельзя использовать self на любом другом получателе
  end

  private 
    def my_age
    	@age
    end
    def age=(age)
      @age = age
    end
end

Person.new(24) # => 24