Статьи

Понимание архитектуры Model-View-Controller (MVC) в Rails

Ниже приводится небольшая выдержка из нашей книги « Rails: новичок к ниндзя», 3-е издание , написанной Гленном Гудричем и Патриком Ленцем. Это руководство для начинающих по Rails. Члены SitePoint Premium получают доступ к своему членству, или вы можете купить копию в магазинах по всему миру.

Архитектура модель-представление-контроллер (MVC), с которой мы впервые столкнулись в главе 1, не уникальна для Rails. Фактически, он предшествует как Rails, так и языку Ruby на многие годы. Rails, однако, действительно берет идею разделения данных приложения, пользовательского интерфейса и логики управления на совершенно новый уровень.

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

MVC в теории

MVC — это образец архитектуры программного приложения. Он разделяет приложение на следующие компоненты:

  • Модели для обработки данных и бизнес-логики
  • Контроллеры для обработки пользовательского интерфейса и приложения
  • Представления для обработки объектов графического интерфейса пользователя и представления

Это разделение приводит к тому, что пользовательские запросы обрабатываются следующим образом:

  1. Браузер (на клиенте) отправляет запрос на страницу контроллеру на сервере.
  2. Контроллер получает необходимые данные из модели, чтобы ответить на запрос.
  3. Контроллер передает полученные данные в представление.
  4. Представление отображается и отправляется обратно клиенту для отображения в браузере.

Этот процесс показан на рисунке 4-2 ниже.

MVC в Rails

Разделение программного приложения на эти три отдельных компонента является хорошей идеей по ряду причин, в том числе:

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

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

  • возможность повторного использования — модель может быть повторно использована несколькими представлениями

Если вы изо всех сил пытаетесь понять концепцию MVC, не волнуйтесь. На данный момент важно помнить, что ваше Rails-приложение разделено на три отдельных компонента. Вернитесь к диаграмме MVC, если вам понадобится обратиться к ней позже.

MVC Путь Рельсов

Rails продвигает концепцию, согласно которой модели, представления и контроллеры следует хранить отдельно, храня код для каждого элемента в виде отдельных файлов в отдельных каталогах.

Именно здесь вступает в игру структура каталогов Rails, которую мы создали еще в главе 2. Пришло время немного покопаться в этой структуре. Если вы загляните в каталог app , показанный на рис. 4-3, вы увидите несколько папок, названия которых могут показаться знакомыми.

Как видите, каждый компонент архитектуры модель-представление-контроллер имеет свое место в подкаталоге app подкаталогах models , views и controllers . (Мы поговорим об assets в главе 7, о helpers в главе 6 и о mailers в этой главе. jobs и channels выходят за рамки этой книги.)

Это разделение продолжается в коде, который содержит сам фреймворк. Классы, которые формируют основную функциональность Rails, находятся в следующих модулях:

ActiveRecord
ActiveRecord — это модуль для обработки бизнес-логики и связи с базой данных. Он играет роль модели в нашей архитектуре MVC. Хотя может показаться странным, что в ActiveRecord нет слова «модель» в названии, для этого есть причина: Active Record — это также имя известного шаблона проектирования, который этот компонент реализует для выполнения своей роли. в мире MVC. Кроме того, если бы он назывался ActionModel , он звучал бы скорее как переплаченная голливудская звезда, чем как программный компонент…
ActionController
ActionController — это компонент, который обрабатывает запросы браузера и облегчает связь между моделью и представлением. Ваши контроллеры будут наследовать от этого класса. Он является частью библиотеки ActionPack , коллекции компонентов Rails, которую мы подробно рассмотрим в главе 5.
ActionView
code> ActionView — это компонент, который обрабатывает представление страниц, возвращаемых клиенту. Представления наследуются от этого класса, который также является частью библиотеки ActionPack .

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

Модуль ActiveRecord

ActiveRecord предназначен для обработки всех задач приложения, связанных с базой данных, включая:

  • установление соединения с сервером базы данных
  • извлечение данных из таблицы
  • хранение новых данных в базе данных

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

База данных Абстракция

ActiveRecord поставляется с адаптерами базы данных для подключения к SQLite, MySQL и PostgreSQL. Большое количество адаптеров доступно для других популярных пакетов серверов баз данных, таких как Oracle, MongoDB и Microsoft SQL Server, через RubyGems.

Модуль ActiveRecord основан на концепции абстракции базы данных. Как следует из главы 1, абстракция базы данных — это способ кодирования приложения, чтобы он не зависел от какой-либо одной базы данных. Код, специфичный для конкретного сервера базы данных, надежно скрыт в ActiveRecord и вызывается по мере необходимости. В результате приложение Rails не связано с каким-либо конкретным программным обеспечением сервера базы данных. Если вам потребуется изменить базовый сервер базы данных позднее, никаких изменений в коде приложения не требуется.

Примечание: жюри на ActiveRecord

Как я уже сказал, ActiveRecord является реализацией шаблона Active Record. Есть те, кто не согласен с подходом, принятым ActiveRecord , поэтому вы тоже много об этом услышите. А пока я предлагаю вам узнать, как работает ActiveRecord , а затем составить свое мнение о реализации по мере изучения.

Вот некоторые примеры кода, которые сильно различаются между поставщиками и которые абстрагируются ActiveRecord :

  • процесс входа на сервер базы данных
  • дата расчета
  • обработка логических ( true / false ) данных
  • эволюция вашей базы данных

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

Таблицы базы данных

Таблицы — это контейнеры в реляционной базе данных, которые структурируют наши данные и состоят из строк и столбцов. Строки отображаются на отдельные объекты, а столбцы — на атрибуты этих объектов. Коллекция всех таблиц в базе данных и связи между этими таблицами называются схемой базы данных . Пример таблицы показан на рисунке 4-4.

В Rails именование классов Ruby и таблиц базы данных следует интуитивно понятному шаблону: если у нас есть таблица с именем stories , состоящая из пяти строк, эта таблица будет хранить данные для пяти объектов Story . Что хорошо в отображении между классами и таблицами, так это то, что нет необходимости писать код для достижения этого; сопоставление просто происходит, потому что ActiveRecord выводит имя таблицы из имени класса.

Обратите внимание, что имя нашего класса в Ruby является существительным в единственном числе ( Story ), но имя таблицы во множественном числе ( stories ). Это отношение имеет смысл, если подумать: когда мы ссылаемся на объект Story в Ruby, мы имеем дело с одной историей. Но таблица SQL содержит множество историй, поэтому ее имя должно быть множественным. Хотя вы можете переопределить эти соглашения — что иногда необходимо при работе с устаревшими базами данных — их гораздо проще придерживаться.

Тесная связь между объектами и таблицами распространяется еще дальше. Если бы в нашей таблице stories был столбец link , как в нашем примере на рис. 4-4, данные в этом столбце были бы автоматически сопоставлены с атрибутом link в объекте Story . А добавление нового столбца в таблицу приведет к тому, что атрибут с таким же именем станет доступным во всех соответствующих объектах этой таблицы.

Итак, давайте создадим несколько таблиц для хранения историй, которые мы создаем.

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

 $ sqlite3 db/development.sqlite3 

После запуска консоли SQLite вставьте следующее:

 CREATE TABLE stories ( "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255) DEFAULT NULL, "link" varchar(255) DEFAULT NULL, "created_at" datetime DEFAULT NULL, "updated_at" datetime DEFAULT NULL ); 

Вам не нужно беспокоиться о запоминании этих команд SQL для использования в ваших собственных проектах; вместо этого, будьте мужественны, зная, что в главе 5 мы рассмотрим миграции. Миграции — это специальные классы Ruby, которые мы можем написать для создания таблиц базы данных для нашего приложения без использования SQL вообще.

Примечание: найдите несколько SQL-умов

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

Использование консоли Rails

Теперь, когда у нас есть таблица stories , давайте .quit консоли SQLite (просто введите .quit ) и откроем консоль Rails. Консоль Rails похожа на интерактивную консоль Ruby ( irb ), которую мы использовали в главе 2, но с одним ключевым отличием. В консоли Rails у вас есть доступ ко всем переменным и классам среды, которые доступны вашему приложению во время его работы. Они не доступны в стандартной консоли irb .

Чтобы войти в консоль Rails, перейдите в папку readit и введите команду rails console или rails c , как показано в следующем коде. Приглашение >> готово принять ваши команды:

 $ cd readit $ rails console Loading development environment (Rails 5.0.0) >> 

Сохранение объекта

Чтобы начать использовать ActiveRecord , просто определите класс, который наследуется от ActiveRecord::Base . Мы очень кратко коснулись оператора :: в главе 3, где упоминали, что это был способ вызова методов класса для объекта. Он также может быть использован для ссылки на классы, которые существуют в модуле, что мы и делаем здесь. Вернитесь к разделу об объектно-ориентированном программировании (ООП) в главе 3, если вам нужно освежить информацию о наследовании.

Рассмотрим следующий фрагмент кода:

 class Story < ActiveRecord::Base end 

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

Из консоли Rails давайте создадим этот класс Story и экземпляр класса под названием story , введя следующие команды:

 >> class Story < ActiveRecord::Base; end => nil >> story = Story.new => #<Story id: nil, name: nil, url: nil, created_at: nil, updated_at: nil> >> story.class => Story(id: integer, name: string, link: string, created_at: datetime, updated_at: datetime) 

Как вы можете видеть, синтаксис для создания нового объекта ActiveRecord идентичен синтаксису, который мы использовали для создания других объектов Ruby в главе 3. На этом этапе мы создали новый объект Story ; однако этот объект существует только в памяти — мы еще не сохранили его в нашей базе данных.

Мы можем подтвердить, что наш объект Story не был сохранен, проверив возвращаемое значение new_record? метод:

 >> story.new_record? => true 

Поскольку объект еще не сохранен, он будет потерян при выходе из консоли Rails. Чтобы сохранить его в базе данных, мы вызываем метод save объекта:

 >> story.save => true 

Теперь, когда мы сохранили наш объект (возвращаемое значение true указывает, что метод сохранения был успешным), наша история больше не является новой записью. Ему даже был присвоен уникальный идентификатор:

 >> story.new_record? => false >> story.id => 1 

Определение отношений между объектами

Помимо основных функциональных возможностей, которые мы только что увидели, ActiveRecord делает процесс определения отношений (или ассоциаций) между объектами настолько простым, насколько это возможно. Конечно, с некоторыми серверами баз данных возможно определить такие отношения полностью в схеме базы данных. Чтобы пройти ActiveRecord через его шаги, давайте посмотрим, как он определяет эти отношения внутри Rails.

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

  • индивидуальные ассоциации
  • ассоциации один ко многим
  • ассоциации многие ко многим

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

Предположим, что наше приложение имеет следующие ассоциации:

  • Author может иметь один Blog :

     class Author < ActiveRecord::Base has_one :weblog end 
  • Author может представить много Stories :

     class Author < ActiveRecord::Base has_many :stories end 
  • Story принадлежит Author :

     class Story < ActiveRecord::Base belongs_to :author end 
  • Story имеет и принадлежит к множеству различных тем:

     class Story < ActiveRecord::Base has_and_belongs_to_many :topics end class Topic < ActiveRecord::Base has_and_belongs_to_many :stories end 

Вы, несомненно, устали от ввода определений классов в консоль, только чтобы они исчезали, как только вы выходите из консоли. По этой причине мы пока не будем углубляться в связи между нашими объектами — вместо этого мы углубимся в модуль Rails ActiveRecord более подробно в главе 5.

Библиотека ActionPack

ActionPack — это имя библиотеки, которая содержит части представления и контроллера архитектуры MVC. В отличие от модуля ActiveRecord , эти модули имеют более интуитивно понятные названия: ActionController и ActionView .

Изучение логики приложения и логики представления в командной строке не имеет большого смысла; В конце концов, представления и контроллеры предназначены для взаимодействия с веб-браузером! Вместо этого я предоставлю краткий обзор компонентов ActionPack и расскажу о практических ActionPack в главе 5.

ActionController (Контроллер)

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

  • решить, как обрабатывать конкретный запрос (например, отображать ли полную страницу или только одну ее часть)
  • извлечение данных из модели для передачи в представление
  • сбор информации из запроса браузера и использование ее для создания или обновления данных в модели

Когда мы представили диаграмму MVC на рисунке 4-2 ранее в этой главе, вам, возможно, не пришло в голову, что приложение Rails может состоять из нескольких различных контроллеров. Ну, это может! Каждый контроллер отвечает за определенную часть приложения.

Для нашего приложения Readit мы создадим:

  • один контроллер для отображения ссылок на истории, который мы StoriesController
  • другой контроллер для обработки аутентификации пользователя, называемый SessionsController
  • контроллер для отображения пользовательских страниц с именем UsersController
  • контроллер для отображения страниц комментариев с именем CommentsController
  • конечный контроллер для обработки истории голосования, называемый VotesController

Каждое приложение Rails поставляется с ApplicationController (который находится в app/controllers/application_controller.rb ), который наследуется от ActionController::Base . Все наши контроллеры будут наследоваться от ApplicationController Между этим классом и классом ActionController::Base будет существовать промежуточный класс; однако это не меняет того факта, что ActionController::Base является базовым классом, от которого наследуется каждый контроллер. Мы StoriesController о StoriesController класса StoriesController более подробно в главе 5. Но у них будет другая функциональность, которая реализована как методы экземпляра. Вот пример определения класса для класса StoriesController :

 class StoriesController < ApplicationController def index end def show end end 

Это простое определение класса устанавливает в StoriesController два пустых метода: метод index и метод show . Мы расширим эти методы в следующих главах.

Каждый контроллер находится в своем собственном файле Ruby (с расширением .rb ), который находится в каталоге app/controllers . StoriesController класс StoriesController который мы только что определили, будет app/controllers/stories_controller.rb в файле app/controllers/stories_controller.rb .

Примечание. Соглашения об именах классов и файлов.

Вы уже заметили, что имена классов и файлов следуют различным соглашениям:

  • Имена классов пишутся в CamelCase (каждое слово начинается с заглавной буквы, без пробелов между словами). На самом деле существует две разновидности CamelCase: одна с заглавной первой буквой (также известная как PascalCase), а другая с заглавной первой буквой. Соглашение Ruby для имен классов требует заглавной первой буквы.

  • Имена файлов пишутся строчными буквами, с подчеркиванием, разделяющим каждое слово.

Это важная деталь. Если это соглашение не будет соблюдено, Rails будет трудно найти ваши файлы. К счастью, вам не нужно будет называть файлы вручную очень часто, если вообще когда-либо, как вы увидите, когда мы рассмотрим сгенерированный код в главе 5.

ActionView (вид)

Как обсуждалось ранее, одним из принципов MVC является то, что представление должно содержать только логику представления. Этот принцип гласит, что код в представлении должен выполнять только те действия, которые связаны с отображением страниц в приложении; ни один код в представлении не должен выполнять какую-либо сложную логику приложения, а также сохранять или извлекать какие-либо данные из базы данных. В Rails все, что отправляется в веб-браузер, обрабатывается представлением.

Как и ожидалось, представления хранятся в папке app/views нашего приложения.

Представление вообще не обязательно должно содержать какой-либо код Ruby — это может быть случай, когда одно из ваших представлений представляет собой простой файл HTML; однако более вероятно, что ваши представления будут содержать комбинацию кода HTML и Ruby, что сделает страницу более динамичной. Код Ruby встроен в HTML с использованием встроенного синтаксиса Ruby (ERb).

ERb позволяет распределять код на стороне сервера по всему HTML-файлу, оборачивая этот код в специальные теги. Например:

 <strong><%= 'Hello World from Ruby!' %></strong> 

Существует две формы пары тегов ERb: одна включает знак равенства, а другая без него:

<%= … %>
Эта пара тегов предназначена для регулярного вывода. Вывод выражения Ruby между этими тегами будет отображаться в браузере.
<% … %>
Эта пара тегов предназначена для исполнения. Вывод выражения Ruby между этими тегами не будет отображаться в браузере.

Вот пример каждого тега ERb:

 <%= 'This line is displayed in the browser' %> <% 'This line executes silently, without displaying any output' %> 

Вы можете поместить любой код Ruby — простой или сложный — между этими тегами.

Создание экземпляра вида немного отличается от модели или контроллера. Хотя ActionView::Base (родительский класс для всех представлений) является одним из базовых классов для представлений в Rails, создание экземпляра представления полностью ActionView модулем ActionView . Единственный файл, который разработчик Rails должен изменить, — это шаблон, который содержит код представления для представления. Как вы уже догадались, эти шаблоны хранятся в папке app/views .

Как и во всем остальном Rails, строгое соглашение касается именования и хранения файлов шаблонов:

  • Шаблон имеет однозначное соответствие действию (методу) контроллера. Имя файла шаблона совпадает с именем действия, с которым он сопоставляется.
  • Папка, в которой хранится шаблон, названа в честь контроллера.
  • Расширение файла шаблона имеет два аспекта и зависит от его типа и языка, на котором написан шаблон. По умолчанию в Rails есть три типа расширений:

    html.erb
    Это расширение для стандартных шаблонов HTML, которые содержат теги ERb.
    xml.builder
    Это расширение используется для шаблонов, которые выводят XML (например, для создания RSS-каналов для вашего приложения).
    json.builder
    Это расширение используется для шаблонов, которые выводят JSON, который является общей интеграцией данных для API. Мы поговорим больше о JSON в главе 9, посвященной сложным темам.

Это соглашение может показаться сложным, но на самом деле оно довольно интуитивно понятно. Например, рассмотрим класс StoriesController определенный ранее. Вызов метода show для этого контроллера по умолчанию попытается отобразить шаблон ActionView который находится в каталоге app/views/stories ActionView . Предполагая, что страница была стандартной HTML-страницей (содержащей некоторый код ERb), имя этого шаблона будет show.html.erb .

Rails также поставляется со специальными шаблонами, такими как макеты и партиалы. Макеты — это шаблоны, которые управляют глобальным макетом приложения, например структуры, которые остаются неизменными между страницами (например, основное меню навигации). Частицы — это специальные подшаблоны (результат разбивки шаблона на отдельные файлы, такие как вторичное навигационное меню или форма), которые могут использоваться в приложении несколько раз. Мы рассмотрим как макеты, так и частичные части в главе 7.

Связь между контроллерами и представлениями происходит через переменные экземпляра, которые заполняются внутри действия контроллера. Давайте расширим наш пример класса StoriesController чтобы проиллюстрировать этот момент (пока не нужно ничего печатать):

 class StoriesController < ActionController::Base def index @variable = 'Value being passed to a view' end end 

Как видите, переменной экземпляра @variable присваивается строковое значение в действии контроллера. Благодаря магии ActionView , на эту переменную теперь можно ссылаться непосредственно из соответствующего представления, как показано в этом коде:

 <p>The instance variable @variable contains: <%= @variable %></p> 

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

Rails также предоставляет доступ к специальным контейнерам, таким как params и хеши session . Они содержат такую ​​информацию, как текущий запрос страницы и сеанс пользователя. Мы будем использовать эти хеши в следующих главах.