Rails — это полноценный фреймворк для веб-приложений, созданный на красивом и выразительном языке программирования Ruby. Фреймворк основан на архитектуре MVC: Model-View-Controller. Rails упорядочивает файлы для моделей, представлений и контроллеров, где логика может быть написана изолированно, но интерактивность между MVC управляется Rails. Rails основан на принципе Соглашение превыше конфигурации, где следование соглашениям, установленным Rails, значительно поможет вам и ускорит процесс разработки. Rails — это фреймворк с батарейками, в котором все необходимые файлы, папки и конфигурация уже сделаны за вас, и вы можете сразу приступить к разработке своего веб-приложения. Еще один принцип, на котором вращается Rails, – Не повторяйтесь: СУХОЙ, который пытается гарантировать, что разработчику не нужно писать одни и те же строки кода для нескольких сценариев. Принцип DRY применяется с помощью различных обратных вызовов, помощников и соглашений, которые предоставляет Rails. В целом, Rails — это фантастическая среда для разработки веб-приложений.

Эта статья НЕ является учебником по Rails, а представляет собой простую попытку понять, как Rails использует Ruby и этот волшебный способ. Прошло два месяца с тех пор, как я начал работать с Ruby on Rails, и это мой любимый фреймворк. Когда я начинал как абсолютный новичок, я просто писал несколько строк кода, создавал файл html.erb, следуя некоторому руководству, и бум! Вы создали страницу, маршрут, модель и контроллер. Было несколько трудно уложить в голове то, что я делал. Такие команды, как rails generate scaffold Person name:string age:integer, будут генерировать Person модель, форму для создания человека, контроллер для управления его маршрутами, и мне даже не пришлось писать ни строчки кода! Это было просто волшебно, и, честно говоря, мне потребовалось некоторое время, чтобы освоиться. Мало того, использование различных обратных вызовов, действий, помощников и соглашений делает так много тяжелой работы для разработчика и упрощает работу, что просто добавляет магии. основы моделей и постараюсь сделать так, чтобы мне и читателю было немного удобнее с красотой и волшебством, которыми обладает Ruby on Rails. В статье предполагается, что читатель знаком с основами Rails и MVC.

Во-первых, Ruby «волшебный».

Ruby — это язык программирования, оптимизированный для удобства разработчиков. Он динамичный, простой в освоении и написании, и выразительный. Это чистый объектно-ориентированный язык, в котором все, от строк до чисел и массивов, является объектом. Существует исчерпывающий список методов, встроенных для каждого основного объекта (массив, строка, число и т. д.), которые могут помочь вам написать читаемый и короткий код. Некоторые операторы кода даже читаются как английские предложения.

Также важно знать концепцию yield и block в Ruby. Блок – это фрагмент кода, который можно вставить в метод. В то время как Yield позволяет нам внедрить этот блок кода где-нибудь в наш метод. Это может показаться достаточно простым, новичкам в Ruby это может показаться загадочным, но в конце концов вы освоитесь. Эти концепции важны, потому что Rails часто их использует.

Следует помнить одну вещь: почти все в Ruby является объектом. Что это значит, мы можем наследовать некоторые фундаментальные объекты, а затем расширять их определение. Рассмотрим эту строку кода: puts 1.methods. Это напечатает все методы для объекта 1 (в данном случае имя класса нашего объекта — Integer — версия Ruby: 3.1.2).

Теперь рассмотрим следующий блок кода:

class AmazingInteger < Integer
end

print Integer.superclass # Numeric
print Integer.subclasses # [AmazingInteger]

Сначала он напечатает родительский класс Integer, то есть Numeric. Затем он напечатает все классы, наследуемые от Integer, в данном случае это только наш пользовательский класс: AmazingInteger. Этот пример показывает, что в Ruby так много встроенных методов, даже для самых основных объектов, которые позволяют нам проверять классы и расширять их поведение. Эта мощная функция помогает нам создавать собственные объявления классов с помощью наших собственных замечательных методов!

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

# Create Procs
proc_square = Proc.new { |x| p x**2 }
proc_cube = Proc.new { |x| p x**3 }

# Append procs in a list
procs = []
procs.push(proc_square)
procs.push(proc_cube)

# Execute procs
# Prints square and cube of 2: 4 and 8
procs.each { |proc| proc.call(2) }

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

И последнее, прежде чем мы двинемся дальше: Символы. Символы — это идентификаторы, которые используются для представления переменных экземпляра и имен методов. На картинке выше видно, что .methods перечислил все методы как символы (например, :anybits). Символы широко используются в хэшах в качестве уникальных ключей. Каждый символ уникален и имеет связанный с ним идентификатор. Символы неизменны и не могут быть изменены. Но почему символы важны? Как уже было сказано, символы действуют как идентификаторы. Рассмотрим следующие строки кода:

class AmazingClass
  attr_accessor :amazing_attribute
  def amazing_method; puts 'I am an amazing method'; end
end

instance = AmazingClass.new
instance.amazing_attribute = 10
puts instance.amazing_attribute # 10
puts instance.public_send(:amazing_method) # 'I am an amazing method'

В приведенном выше коде мы сначала объявили метод доступа к атрибуту (чтобы мы могли читать и записывать значение экземпляра) для нашего класса, используя символ :amazing_attribute, и вы можете видеть ниже, что он работает хорошо. Во-вторых, мы создали метод для нашего класса и вызвали его, используя public_send (public_send позволяет нам вызывать общедоступные методы с помощью символов), а символ :amazing_method правильно определил, какой метод мы хотим выполнить в нашем классе. В Ruby мы можем делать гораздо больше символов.

Таким образом, Ruby является одним из самых высокоуровневых языков, основная сила которого заключается в его выразительности и возможностях. Всю реализацию Rails можно резюмировать следующими словами: Rails использует потенциал Ruby для создания пользовательских классов и модулей, расширения определения классов и модулей и предоставления синтаксического сахара для многих методов. Подготовка Rails-подобной среды на языке программирования, за которым не стоит столько «магии», будет чрезвычайно сложной, поскольку большая часть возможностей, которыми обладает Rails, исходит от языка, на котором он построен: Ruby.

Это было тривиальное введение в Ruby. Мы не можем обсуждать Rails без Ruby. Теперь мы можем перейти к Rails и попытаться изучить его.

Код для большинства строительных блоков в Rails, таких как ActiveRecord (модель M), ActionController (контроллер C), ActionView (представление V) и т. д., имеет аналогичный организованный шаблон.

  • Существует базовый модуль, который компилирует все подмодули в файл.
  • Подмодули находятся в каталоге lib/. Эти подмодули имеют реализацию различных аспектов базового модуля.

Описывать все, что касается Rails, в статье нецелесообразно, так как в нем так много удивительных вещей, которые невозможно описать в короткой статье. В этой статье я дам краткий обзор ActiveRecord. Но сначала нам нужно изучить ActiveSupport.

ActiveSupport — это ступенька почти для всех модулей Rails

ActiveSupport предоставляет утилиту и богатый модуль расширений, который состоит из ряда методов для приложений Rails. Почти все классы и модули включают ActiveSupport в свое определение. Спектр утилит, предоставляемых ActiveSupport, огромен. Некоторые модули в ActiveSupport предоставляют вспомогательные методы для массивов, строк, событий и т. д., которые упрощают определенные задачи. Другие предоставляют методы, которые действуют как синтаксический сахар для управления зависимостями, автоматической загрузки библиотек и упрощения настройки приложения. Список хелперов огромен и описывать все в одной статье — не лучшая идея. Главный вывод: ActiveSupport «расширяет» предоставляемые Ruby утилиты и расширения, а ActiveRecord, ActionController и т. д. извлекают выгоду из этих утилит.

ActiveModel является основой ActiveRecord.

Разница между ActiveModel и ActiveRecord минимальна в определении. Я хотел бы процитировать ответ от Stackoverflow, который в значительной степени резюмирует разницу между ними: ActiveModel + Поддержка базы данных = ActiveRecord. Хотя разница в определении невелика, ActiveRecord можно рассматривать как расширение ActiveModel. В Rails много моделей, поэтому я ограничу обсуждение обратными вызовами, проверками и атрибутами. здесь.

Обратные вызовы

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

class Person < ApplicationRecord
  after_create :create_callback

  def create_callback; puts 'Create Callback'; end
end

У нашей модели Person есть имя и возраст. Мы объявили два обратных вызова для нашей модели и сохранили имена методов под after_create. Rails предоставляет нам консоль, где мы можем поиграть и проверить наше приложение Rails. Таким образом, консоль будет очень полезна для анализа обратных вызовов.

Обратные вызовы для модели хранятся в атрибуте класса __callbacks. __callbacks – это хэш, ключи которого представляют собой события (такие как создание, уничтожение, обновление и т. д.), а значения – CallbackChain из модуля ActiveSupport::Callbacks. Поэтому, когда мы объявляем наш обратный вызов с указанным действием (например, after_create), эти обратные вызовы сохраняются в виде пары ключ-значение под __callbacks под указанным действием (в данном случае: :create). Нас интересуют только обратные вызовы :create, поэтому мы можем получить их, введя Person.__callbacks[:create] в консоли rails. На выходе будут напечатаны детали обратного вызова, который имеет мьютекс, конфигурацию и цепочку обратных вызовов. Затем обратные вызовы компилируются с использованием CallbackChain#compile, который возвращает CallbackSequence. CallbackSequence имеет экземпляр Proc под переменной экземпляра :@after (экземпляр :@before пуст, потому что у нас нет обратного вызова в разделе до). Этот Proc затем выполняется для нашего Person, когда мы создаем нового человека. Короче говоря, если я приукрашиваю это, все сводится к Proc и Mutex. Мьютекс помогает синхронизировать наши обратные вызовы, в то время как наш Proc содержит блоки кода, которые мы выполняем для наших обратных вызовов. Опять же, это возможно только благодаря тому, насколько прекрасен Ruby.

Валидации

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

class Person < ApplicationRecord
  after_create :create_callback
  
  validate :name, presence: true, uniqueness: true

  def create_callback; puts 'Create Callback'; end
end

Теперь, если вы наберете Person._validators в консоли Rails, вы найдете два валидатора: один PresenceValidator, а другой UniquenessValidator. Это означает, что валидаторы на самом деле являются классами. Почти все валидаторы унаследованы от базового класса: EachValidator, у которого есть абстрактный метод validate_each. Все дочерние классы, унаследованные от EachValidator, должны переопределить метод validate_each на основе выполняемой ими проверки. Некоторые валидаторы запускают SQL-запросы для проверки (например, UniquenessValidator), а другие нет. В общем, валидаторы — это класс, который выполняет определенные проверки. На основе проверок запись может быть сохранена в базе данных или нет.

Но есть один вопрос: когда выполняются валидации? Прежде всего, когда мы объявили validate :name, presence: true, Rails определил, что нам нужен валидатор присутствия, основанный на символе :presence, и использует этот символ для добавления класса PresenceValidator. Теперь, если вы наберете Person.__callbacks[:validate], вы поймете, что проверки на самом деле являются частью цепочки обратного вызова. Так что да, они рассчитаны так же, как и обратные вызовы, и эти валидаторы обычно синхронизируются в самом начале перед любым другим обратным вызовом. Теперь атрибуты.

Атрибуты

Просто написав rails generate model Person name:string age:integer, я создал класс модели со всеми упомянутыми атрибутами по их типам, но я этого не сделал. Я не писал полноценный класс со всеми атрибутами, такими как атрибуты, являющиеся переменной экземпляра или что-то в этом роде, но я это сделал. Так что да, атрибуты были чем-то, чего я не понял с самого начала. Я не понял, где упоминаются и хранятся эти атрибуты.

Для удобства давайте рассмотрим пример (приведенный ниже пример и объяснение не совсем корректны, когда речь идет о Rails, но являются попыткой дать поверхностное представление об атрибутах):

class Person
  attr_reader :name, :age

  def initialize
    # Default values for our attributes
    @name = 'undefined'
    @age = 0
  end

  def assign_attributes(new_values)
    # new_values: hash
    new_values.each do |attribute_name, attribute_value|
      setter = :"#{attribute_name.to_s}="
      public_send(setter, attribute_value)
    end
  end

  def name=(name); @name = name; end
  def age=(age); @age = age; end

  def print_person
    puts "Name: #{@name} | Age: #{@age}"
  end
end

Давайте рассмотрим наш класс Person. Во-первых, у него есть два атрибута: имя и возраст, и мы можем прочитать эти атрибуты вне класса благодаря attr_reader. Далее у него есть три метода:

  1. :assign_attributes; который принимает хеш, где ключ — это имя атрибута, а значение — это новое значение, которое будет присвоено
  2. :name= и :age; которые вы можете рассматривать как виртуальные сеттеры. Итак, передав person.name = 'Name', мы заново изменили имя на «Имя».
  3. :print_person; который печатает имя и возраст человека.

Теперь рассмотрим следующие строки кода:

person = Person.new
person.print_person # Prints: "Name: undefined | Age: 0"
person.assign_attributes(name: 'New Name', age: 20)
person.print_person # Prints: "Name: New Name | Age: 20"

Основной метод, который нуждается в подробном объяснении, это метод :assign_attributes. Как уже было сказано, он принимает хэш в качестве входных данных. Теперь, если мы зациклимся на хеше, у нас будут ключи, которые являются символами, и соответствующие им новые значения. Теперь в этом методе проверьте переменную setter. Если входящий ключ :name, результирующий сеттер будет :name=. В начале мы уже установили, что символы могут использоваться для идентификации методов, и вы можете видеть, что :name= — это метод в нашем (виртуальном установщике) нашего класса. Теперь мы используем метод public_send для передачи идентификатора метода setter и выполняем этот метод, чтобы присвоить новое значение имени нашего человека.

Это очень поверхностный и беглый взгляд на назначение атрибутов в Rails. В Rails есть множество методов, созданных для наших атрибутов. Что делает Rails, так это то, что когда мы используем наш пользовательский атрибут для нашей модели, Rails присваивает своим предварительно созданным методам псевдонимы с именем атрибута. Например, если для атрибута существует метод clear_attribute, Rails присваивает ему псевдоним clear_name для нашего атрибута :name; def attribute=(attribute) будет иметь псевдоним def name=(attribute). Опять же, это всего лишь беглый взгляд на атрибуты. Атрибутов гораздо больше, что скрыть их практически невозможно. Атрибуты сами по себе заслуживают полной, длинной и подробной статьи. И снова все сводится к одному: Руби. Именно Ruby позволяет нам объявлять такие методы и функциональные возможности.

Краткое содержание

Статья была попыткой демистифицировать Rails и Ruby. Мы рассмотрели только базовую модель Ruby и Rails, и вы уже можете видеть, что большая часть материала сводится к Ruby и мощным функциям, которыми он обладает. Оглядываясь назад, можно сказать, что статья представила процедуры, классы, символы, виртуальные атрибуты и другие функции Ruby на самом базовом уровне. Конечно, статья представляет собой беглый обзор Rails и имеет множество ограничений. Некоторые части объяснений чрезмерно упрощены и лишены четкой глубины. Определенно не хватает глубокого понимания возможностей Rails. Но, надеюсь, это улучшило ваше понимание Ruby и Rails и несколько демистифицирует их для вас. Короче говоря, в Rails нет ничего волшебного. Просто Руби так хорош.