Отказ от ответственности: то, что вы собираетесь прочесть, исходит от человека, который три дня назад начал изучать паттерн модель-представление-контроллер (MVC) и Ruby on Rails в рамках Launch School. Эта серия написана в основном как инструмент для меня, чтобы организовать свои мысли. Если другие также находят это полезным, то это делает его еще лучше. Наслаждаться.
Подготовка
В этом проекте я буду использовать Ruby 2.0.0 и Ruby on Rails 4.0.0. Почему? Launch School использовала его в то время. Если вы следуете этому примеру, убедитесь, что ruby -v
выводит 2.0, а rails -v
выводит 4.0. Если нет, иди за ними.
Начинать
Мы создадим простое приложение для управления учетными записями с использованием Ruby on Rails, в котором держатели могут иметь учетные записи и совершать транзакции. Я знаю, что это скучно, но это должно позволить нам пройтись по основам приложения rails, не углубляясь слишком далеко.
Чтобы запустить приложение Rails, перейдите в каталог, в котором вы хотите разместить основную папку проекта, и запустите rails new accountManager
в терминале. Это создаст папку accountManager
и многочисленные файлы, с большинством из которых мы не будем связываться. (С этого момента все команды терминала должны выполняться из основного каталога приложения.)
Исследуйте файлы, созданные Rails. Пять основных областей файлов проекта, которые нас будут интересовать, находятся в app/controllers
, app/models
, app/views
, config/routes.rb
, db/migrate
. Обратите внимание, что db/migrate
migrate не будет существовать до тех пор, пока не будет создана первая миграция. Все это будет со временем объяснено либо в этом посте, либо позже в этой серии.
Сервер можно запустить с помощью терминальной команды rails server
и просмотреть в localhost:3000
в браузере. Rails должен отображать общую страницу «Добро пожаловать на борт» с несколькими советами о том, как начать работу. Мы не будем следовать этому в точности, так как будем использовать более ручной подход.
Настройка базы данных
Если вы используете SQLite в качестве базы данных, а это потому, что это база данных по умолчанию для Rails, то файл базы данных был создан при сборке приложения. Если у вас почему-то нет файла db/development.sqlite3
, то запустите rake db:create
в терминале.
Чтобы настроить схему базы данных, нам сначала нужен план. Это примет форму диаграммы отношений сущностей (ERD). Мы будем следовать этой дорожной карте для создания схемы и связей между таблицами.
Мы создадим каждую из этих таблиц и их ассоциации одну за другой. Таблицы названы с использованием соглашения Rails для таблиц базы данных. Для справки вот список ассоциаций, которые нам нужно сделать.
- У
Transaction
может быть одинAccount
, а уAccount
может быть многоTransaction
. Это ассоциация «один ко многим». - У
Transaction
может быть одинHolder
, а уHolder
может быть многоTransaction
. Это ассоциация «один ко многим». - У
Holder
может быть многоAccount
, а уAccount
может быть многоHolders
.Account
иHolder
связаны через таблицу соединенийholder_accounts
. Это ассоциация «многие ко многим».
Приведенные выше имена в верхнем регистре станут классами моделей для каждого ресурса.
Запустите rails generate migration create_transactions
, чтобы создать файл миграции, который будет использоваться для создания схемы для таблицы Transaction
. Имя файла <creation_date>_create_transactions.db
будет создано в папке db
, где <creation_date>
представляет время создания файла. Файл должен выглядеть так:
class CreateTransactions < ActiveRecord::Migration def change create_table :transactions do |t| end end end
Rails пошли дальше и добавили метод create_table :transactions
, потому что мы использовали create_transactions
при генерации. Если этого кода нет в вашем, просто добавьте его. Теперь измените файл, включив в него столбцы таблицы, описанные в нашем ERD.
class CreateTransactions < ActiveRecord::Migration def change create_table :transactions do |t| t.string :kind t.integer :amount t.text :description t.timestamps end end end
t.timestamps
— это удобный метод, который указывает Rails создать столбцы created_at
и updated_at
в таблице. Rails будет автоматически поддерживать актуальность этих столбцов всякий раз, когда предпринимается действие. Теперь запустите rake db:migrate
, чтобы применить миграцию и создать таблицу. Вы должны увидеть вывод, который говорит CreateTransactions: migrate
.
(Сначала я пытался использовать type
вместо kind
в качестве имени столбца. Миграция прошла успешно, но позже я столкнулся с проблемами при попытке создать Transaction
. Оказалось, что type
— это ключевое слово, используемое в ActiveRecord
. Помните об этом при создании имена.)
Настройка модели
Модель сделки
Теперь, когда у нас есть настройка базы данных для обработки транзакций, создайте модель Transaction
, которая позволит нам работать с данными. Создайте файл с именем app/models/transaction.rb
. Соглашение Rails состоит в том, чтобы назвать этот файл единственной версией таблицы в нижнем регистре.
class Transaction < ActiveRecord::Base end
Просто создайте класс и наследуйте его от ActiveRecord::Base
. Все классы моделей должны иметь это наследование. Это даст классу все полезные методы для доступа, обновления, создания и всесторонней работы с таблицей транзакций и ее ассоциациями.
На этом этапе мы должны убедиться, что Transaction
правильно подключен к базе данных и что мы можем создавать и сохранять транзакции. Запрыгивайте в rails console
(используя эту команду). Введите Transaction
и просмотрите результат:
> Transaction => Transaction(id: integer, kind: string, amount: integer, description: text, created_at: datetime, updated_at: datetime, holder_id: integer, account_id: integer)
Когда вы вводите Transaction
, должно появиться что-то вроде приведенного выше вывода. Это означает, что информация о таблице была получена. Попробуйте создать новую транзакцию и сохранить ее в базе данных.
> transaction = Transaction.new(kind: "Checking", amount: 1500, description: "check") => #<Transaction id: nil, kind: "Checking", amount: 1500, description: "check", created_at: nil, updated_at: nil> > transaction.save (0.1ms) begin transaction SQL (1.4ms) INSERT INTO "transactions" ("amount", "created_at", "kind", "updated_at") VALUES (?, ?, ?, ?, ?) [["amount", 1500], ["created_at", Sat, 16 Dec 2017 00:21:05 UTC +00:00], ["description", "check"], ["kind", "Checking"], ["updated_at", Sat, 16 Dec 2017 00:21:05 UTC +00:00]] (4.7ms) commit transaction => true
Несколько замечаний: Transaction#new
не сохраняет транзакцию в базе данных; объект хранится только в памяти. Вот почему id
, created_at
и updated_at
становятся nil
при создании экземпляра. Также обратите внимание, что я не указал значения ни для одного из этих столбцов, Rails обработает их, когда транзакция будет сохранена в базе данных. Наконец, я проигнорировал столбцы holder_id
и account_id
, перечисленные в ERD. Мы добавим их при создании этих таблиц и ассоциаций.
Transaction.save
обращается к базе данных и записывает данные в таблицу транзакций. Просмотрите SQL-запрос, созданный для обработки запроса. На данный момент транзакция была успешно сохранена, и наша база данных имеет одну строку в таблице транзакций. Вы можете перепроверить это с помощью Transaction.find(1)
или Transaction.first
. Оба должны вернуть только что созданную и сохраненную транзакцию.
Модель держателя и отношение «один ко многим»
Таким же образом создайте модель Holder
. Я кратко перечислю шаги здесь:
- Запустите
rails generate migration create_holders
, чтобы создать миграцию. - Отредактируйте файл миграции, чтобы указать Rails создать таблицу
Holder
с соответствующими столбцами. Не для отметок времени. - Запустите
rake db:migrate
и создайте модельHolder
. Убедитесь, чтоHolder
правильно подключен в консоли.
Если все прошло хорошо, теперь у вас должен быть новый файл миграции и файл app/models/holder.rb
, аналогичный транзакциям.
# db/yyyymmddhhmmss_create_holders.rb class CreateHolders < ActiveRecord::Migration def change create_table :holders do |t| t.string :name t.timestamps end end end # app/models/holder.rb class Holder < ActiveRecord::Base end
Создание отношения «один ко многим». На этом этапе мы можем работать с транзакциями и держателями, но они никак не связаны. Rails выполняет это с помощью вспомогательных методов, называемых has_one
, has_many
, belongs_to
и has_and_belongs_to_many
. Поскольку у нас отношение «один ко многим», мы будем использовать belongs_to
и has_many
. Измените holder.rb
и transaction.rb
на следующие:
# holder.rb class Holder < ActiveRecord::Base has_many :transactions end # transaction.rb class Transaction < ActiveRecord::Base belongs_to :holder end
Эти два простых дополнения образуют отношения. По соглашению Rails будет искать столбец holder_id
в таблице transactions
при поиске транзакций держателя. Если бы вместо этого мы хотели назвать владельца учетной записи как-то иначе, например «владелец», но у нас все еще есть таблица holders
в базе данных, мы могли бы использовать вместо этого следующее:
# transaction.rb class Transaction < ActiveRecord::Base belongs_to :owner, class_name: :holder, foreign_key: :holder_id end
Мы можем сделать это в определенных ситуациях, чтобы сделать имена более гармонирующими с отношениями, но holder
для нас достаточно прямо сейчас.
В настоящее время в таблице транзакций нет столбца holder_id
. Нам нужно создать его с помощью другой миграции. Запустите rails generate migration add_holder_id_to_transactions
и поместите это в полученный файл.
class AddHolderIdToTransactions < ActiveRecord::Migration def change add_column :transactions, :holder_id, :integer end end
Запустите миграцию с помощью rake db:migrate
. Убедитесь, что Transaction
теперь имеет столбец holder_id
в консоли с rails console
, а затем Transaction.new
.
Подтверждение ассоциации держателя и учетной записи. Теперь нам нужно поиграть с транзакциями и держателями и убедиться, что они связаны. Запустите следующие команды в консоли rails.
transaction = Transaction.create(kind: "deposit", amount: 2000, description: "check")
(create
эффективно запускаетnew
иsave
одновременно.)transaction.holder
(Это должно вернутьnil
, так как в настоящее время нет держателя, связанного с этой транзакцией.)holder = Holder.create(name: "Alex")
holder.transactions
(Это должно вернуть пустую коллекцию, подобную массиву, поскольку с этим держателем не связаны транзакции.)holder.transactions << transaction
(присваиваетholder_id
транзакции и добавляет ее в коллекцию транзакций.)- Пройдите через ассоциации и исследуйте.
holder.transactions.first.holder.name
(должно вернуть имя первоначального владельца) - С другой стороны
transaction.holder.name
(должно вернуть имя первоначального держателя)
Если все вышеперечисленное работает, вы можете быть уверены, что все правильно подключили. Поздравляем, вы создали отношение «один ко многим».
Модель учетной записи и отношение «один ко многим»
В нашем приложении у держателя может быть много транзакций, но эти транзакции должны происходить на счете. Я оставлю эту часть на ваше усмотрение. Используйте модель держателя в качестве примера для создания отношения «один ко многим», когда одна учетная запись может иметь много транзакций. В приведенных ниже решениях указаны файлы, которые были обновлены. Не забудьте запустить db:migrate
после редактирования новых файлов миграции.
# db/yyyymmddhhmmss_create_accounts.rb class CreateAccounts < ActiveRecord::Migration def change create_table :accounts do |t| t.string :name t.string :kind t.integer :balance, default: 0 t.timestamps end end end # db/yyyymmddhhmmss_add_account_id_to_transactions.rb class AddAccountIdToTransactions < ActiveRecord::Migration def change add_column :transactions, :account_id, :integer end end # app/models/account.rb class Account < ActiveRecord::Base has_many :transactions end # app/models/transactions.rb class Transaction < ActiveRecord::Base belongs_to :holder belongs_to :account end
Примечание. Вы можете объединить две миграции в одну, если считаете это необходимым, поскольку на этом этапе вы знаете, что нам понадобится столбец account_id
.
Предупреждение: всегда используйте миграцию для изменения базы данных. Изменение уже запущенных миграций (независимо от того, выполнялись ли они вами или членами вашей команды) опасно. Небольшой пример: если вы удалили столбец из старой миграции, но более поздняя миграция все еще пытается изменить этот столбец. Хотя выполнение чего-то подобного может работать на вашем компьютере, если бы кто-то другой взял ваш код и запустил миграцию, этот конкретный столбец никогда не был бы создан. Ошибка: вы не можете изменить несуществующий столбец.
Отношение «владелец-аккаунт» «многие ко многим»
Окончательное отношение, которое нам нужно установить, позволит владельцу иметь много счетов (как и многие люди), а счету — много держателей (например, совместный расчетный счет).
У нас есть две основные таблицы accounts
и holders
. Теперь нам нужно создать таблицу соединений, чтобы соединить их. Это делается аналогично другим таблицам, но ассоциации могут быть немного сложными.
Сгенерируйте миграцию, чтобы создать таблицу соединений. Имена для таблиц, которые содержат несколько слов, могут быть сложными, поэтому Rails предоставляет вспомогательный метод #tableize
для генерации имени таблицы, которое Rails будет использовать для данной модели. В нашем случае мы хотим, чтобы класс (наша модель) был HolderAccount
. Перейдите в консоль rails и запустите "HolderAccount".tableize
, который должен вернуть "holder_accounts"
. Шаблон для этого обычно <first[singular]_second[plural]>
, все в нижнем регистре. Выйдите из консоли и запустите rails generate migration create_holder_accounts
. Сделайте так, чтобы ваш файл миграции выглядел так:
class CreateHolderAccounts < ActiveRecord::Migration def change create_table :holder_accounts do |t| t.integer :holder_id, :account_id t.timestamps end end end
И мы могли бы также создать модель HolderAccounts
во вновь созданном файле holder_account.rb
. Обратите внимание, что принято использовать оба существительных в единственном числе с подчеркиванием.
class HolderAccount < ActiveRecord::Base end
Запустите rake db:migrate
, затем убедитесь, что HolderAccount
правильно подключен в консоли.
Создание отношения «многие ко многим». Наш последний шаг — настроить отношение «многие ко многим». Вы можете думать об этом отношении как о двух отношениях «один ко многим» с таблицей соединения с дополнительным параметром на концах, указывающим, что они связаны через него.
- Каждая строка
holder_accounts
связана с однимholder
и однимaccount
. - У каждого
account
иholder
может быть многоholder_accounts
. Итак, вот наши отношения.
# holder.rb class Holder < ActiveRecord::Base has_many :transactions has_many :holder_accounts has_many :accounts, through: :holder_accounts end # holder_account.rb class HolderAccount < ActiveRecord::Base belongs_to :holder belongs_to :account end # account.rb class Account < ActiveRecord::Base has_many :transactions has_many :holder_accounts has_many :holders, through: :holder_accounts end
Дополнительная работа здесь, конечно, through: :holder_accounts
. Это бит, который говорит связать holders
и accounts
через их идентификаторы в таблице holder_accounts
.
Подтверждение ассоциации владельца и учетной записи — проверьте! Перед запуском этих команд убедитесь, что у вас есть хотя бы один держатель и одна учетная запись.
holder = Holder.first holder.accounts # return an empty collection account = Account.first account.holders # return an empty collection
Если модели были подключены неправильно, будут выданы ошибки. Вы можете создать их с помощью Holder.create
и Account.create
. Теперь добавьте учетную запись держателя.
holder.accounts << account # assigns the account to the holder holder.accounts.first.name # returns name of first account assigned to holder account.holders.first.name # returns name of first holder assigned to account
Теперь мы можем пройтись по всем нашим данным настолько запутанным образом, насколько нам нравится:
account.holders.first.transactions.first.account.name
Это вернет имя учетной записи первой транзакции первого держателя на исходной учетной записи. Это очень окольный способ доступа к этим данным, но он показывает, что отношения были настроены правильно.
Заворачивать
Мы увидели здесь, что приложение Ruby on Rails может быть создано с относительно небольшими усилиями. Эта структура заботится о многих деталях, предполагая множество соглашений. В частности, мы видели, что имена практически всего принимают определенную форму. Имена классов модели, имена файлов, имена первичных и внешних ключей в базах данных и многое другое должны соответствовать соглашениям Rails. Rails позволяет перезаписать это небольшим количеством кода, но в этом нет необходимости.
Теперь у нашего менеджера по работе с клиентами есть простая, но надежная база данных, с которой мы можем работать в будущем. Что касается шаблона MVC, эта запись касалась только «M». В следующий раз мы будем работать с представлениями и контроллерами, чтобы мы могли видеть наши данные в браузере.