Погружаясь в объектно-ориентированный язык Ruby, вы быстро познакомитесь с его обширной коллекцией библиотек Gem. Многие из этих драгоценных камней были разработаны, чтобы сделать задачи серверной части менее утомительными и несколько автоматизированными. Одна очень удобная жемчужина (и, возможно, самая важная) — это Active Record.

«ActiveRecord — это ORM (объектно-реляционное сопоставление). Это слой кода Ruby, который работает между вашей базой данных и вашим логическим кодом. Когда вам нужно внести изменения в базу данных, вы пишете код Ruby, а затем запускаете «миграции», которые вносят фактические изменения в базу данных». (Encora.com) Это позволяет вам как Ruby-программисту создавать базы данных, запускать указанные базы данных и создавать/читать/обновлять/удалять данные в базах данных без необходимости изучения совершенно отдельного языка, такого как SQL (язык структурированных запросов).

Говоря о создании, чтении, обновлении и удалении, или лучше известном как CRUD, я хотел бы познакомить вас с некоторыми основными методами Active Record/Ruby, которые позволяют вам манипулировать базой данных и понимать строительные блоки внутреннего управления, которые CRUD должен предложить.

Я позволил себе настроить базу данных с двумя таблицами, «владельцы» и «собаки», и заполнить обе некоторыми данными. У них есть ассоциация «один ко многим» — у одного владельца много собак, и собаки принадлежат владельцу. Таблицы строятся следующим образом:

Owners:
Column 1: "name", data type: string
Column 2: "spoils_dog?", data type: boolean (true or false)

Dogs:
Column 1: "name", data type: string
Column 2: "age", data type: integer
Column 3: "breed", data type: string
Column 4: "demeanor", data type: string
Column 5: "owner_id", data type: integer - (this is the foreign key which provides a way for the two tables to be connected)

Создать:

Теперь, когда мы понимаем, с какими данными мы работаем и что нам доступно, давайте начнем с «C» CRUD, создадим.

В любой базе данных нам нужна возможность добавлять релевантную информацию. Для этого есть два очень простых пути: наш первый путь — использовать метод .new, за которым следует метод .save. Давайте проверим это, добавив новый экземпляр собаки в нашу базу данных. Мы начнем с сохранения создания нового экземпляра собаки в переменной с именем «new_dog».

#Method: .new

[36] pry(main)> new_dog = Dog.new
=> #<Dog:0x00007fb17fb2d440 id: nil, name: nil, age: nil, breed: nil, demeanor: nil, owner_id: nil>

Теперь у нас есть новый существующий экземпляр, но недостаток использования .new заключается в том, что он не сохраняет этот экземпляр автоматически в базе данных. В настоящее время этот новый экземпляр просто живет в эфире терминальной консоли. Если мы хотим, чтобы эти данные сохранялись, мы должны формально добавить их в базу данных с помощью .save. Еще одна вещь, о которой следует помнить, это то, что всякий раз, когда мы создаем новый экземпляр, все значения атрибутов будут нулевыми, если не указано иное (мы вернемся к этому в разделе «Обновление» CRUD).

По мере того, как мы продвигаемся вперед с нашим экземпляром «new_dog», нам нужно будет использовать метод .save для обновления нашей базы данных.

#Method: .save

[37] pry(main)> new_dog.save
D, [2022–12–04T20:10:13.198137 #98725] DEBUG - : (0.2ms) begin transaction
D, [2022–12–04T20:10:13.204266 #98725] DEBUG - : Dog Create (1.5ms) INSERT INTO "dogs" DEFAULT VALUES
D, [2022–12–04T20:10:13.207203 #98725] DEBUG - : (1.6ms) commit transaction
=> true

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

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

[38] pry(main)> new_dog
=> #<Dog:0x00007fb17fb2d440 id: 11, name: nil, age: nil, breed: nil, demeanor: nil, owner_id: nil>

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

Второй маршрут, о котором я упоминал ранее, использует методы .new и .save и объединяет их в один шаг. Метод, который мы будем использовать для этого, называется .create. На этот раз давайте добавим новый экземпляр владельца и заполним данные атрибута.

#Method: .create

[40] pry(main)> Owner.create(name: "Emily Elizabeth", spoils_dog?: true)
D, [2022–12–04T20:36:20.316347 #98725] DEBUG - : (2.4ms) begin transaction
D, [2022–12–04T20:36:20.338853 #98725] DEBUG - : Owner Create (1.8ms) INSERT INTO "owners" ("name", "spoils_dog?") VALUES (?, ?) [["name", "Emily Elizabeth"], ["spoils_dog?", "t"]]
D, [2022–12–04T20:36:20.342785 #98725] DEBUG - : (1.1ms) commit transaction
=> #<Owner:0x00007fb1803c88e8 id: 9, name: "Emily Elizabeth", spoils_dog?: true>

Вы можете видеть, что мы вызвали .create для класса Owner и предоставили атрибуты: (имя: «Эмили Элизабет», испорченная собака?: true). Если вы проанализируете 3-ю строку кода, вы снова увидите аналогичную инструкцию SQL, выполняемую для нас Active Record. Это то же самое, что мы видели, когда использовали метод .save для экземпляра нашей новой собаки. Давайте проверим нашу базу данных, чтобы убедиться, что Эмили действительно существует? Поскольку технически Эмили была последним владельцем, добавленным в базу данных, мы можем найти ее по короткому пути.

#Method: .last

[4] pry(main)> Owner.last
D, [2022–12–07T18:25:38.666407 #34293] DEBUG - : Owner Load (1.2ms) SELECT "owners".* FROM "owners" ORDER BY "owners"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<Owner:0x00007fcc94406100 id: 9, name: "Emily Elizabeth", spoils_dog?: true>

Вроде бы она есть, но трижды проверить не помешает…

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

Вот список методов, которые мы использовали для Create:

1. ClassName.new -> creates instance with nil values, not saved

2. instance_name.save -> saves that instance to the database

3. ClassName.create -> creates new instance and saves it to the database

Теперь давайте рассмотрим некоторые методы Read.

Читать:

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

Отличной отправной точкой является метод .all, который вызывается в нашем классе. Этот метод покажет нам каждый экземпляр в нашей таблице для этого конкретного класса. Давайте попробуем это на нашем столе Dogs.

#Method: .all

[6] pry(main)> Dog.all
D, [2022–12–07T18:34:42.381507 #34293] DEBUG - : Dog Load (0.3ms) SELECT "dogs".* FROM "dogs"
=> [#<Dog:0x00007fcc97acc4f0 id: 1, name: "Ace", age: 3, breed: "German Shepherd", demeanor: "loyal", owner_id: 1>,
#<Dog:0x00007fcc97ac7bd0 id: 2, name: "Jax", age: 3, breed: "Siberian Husky", demeanor: "energetic", owner_id: 4>,
#<Dog:0x00007fcc97ac7a68 id: 3, name: "Gizmo", age: 3, breed: "Pug", demeanor: "congested", owner_id: 2>,
#<Dog:0x00007fcc97ac7900 id: 4, name: "Daisy", age: 3, breed: "French Bulldog", demeanor: "stubborn", owner_id: 2>,
#<Dog:0x00007fcc97ac7748 id: 5, name: "Cookie", age: 3, breed: "Miniature Dachshund", demeanor: "cuddly", owner_id: 6>,
#<Dog:0x00007fcc97ac74a0 id: 6, name: "Maya", age: 3, breed: "Australian Shepherd", demeanor: "rambunctious", owner_id: 5>,
#<Dog:0x00007fcc97ac65f0 id: 7, name: "Cannoli", age: 3, breed: "Corgi", demeanor: "lazy", owner_id: 8>,
#<Dog:0x00007fcc97ac6370 id: 8, name: "Lucy", age: 3, breed: "Boston Terrier", demeanor: "springy", owner_id: 7>,
#<Dog:0x00007fcc97ac5d30 id: 9, name: "Bella", age: 8, breed: "Chihuahua", demeanor: "anxious", owner_id: 3>,
#<Dog:0x00007fcc97ac5060 id: 10, name: "Onyx", age: 2, breed: "Great Dane", demeanor: "dopey", owner_id: 8>,
#<Dog:0x00007fcc97ac45c0 id: 11, name: nil, age: nil, breed: nil, demeanor: nil, owner_id: nil>]

Теперь давайте проверим нашу таблицу Owners.

#Method: .all

[7] pry(main)> Owner.all
D, [2022–12–07T18:35:49.348853 #34293] DEBUG - : Owner Load (0.3ms) SELECT "owners".* FROM "owners"
=> [#<Owner:0x00007fcc9433cc88 id: 1, name: "Kyle", spoils_dog?: false>,
#<Owner:0x00007fcc9433cad0 id: 2, name: "Stephanie", spoils_dog?: true>,
#<Owner:0x00007fcc9433c8f0 id: 3, name: "Jared", spoils_dog?: true>,
#<Owner:0x00007fcc9433c788 id: 4, name: "Tami", spoils_dog?: false>,
#<Owner:0x00007fcc9433c5a8 id: 5, name: "Beth", spoils_dog?: false>,
#<Owner:0x00007fcc9433c440 id: 6, name: "Todd", spoils_dog?: true>,
#<Owner:0x00007fcc9433c238 id: 7, name: "Richard", spoils_dog?: false>,
#<Owner:0x00007fcc9433c0f8 id: 8, name: "Eleanor", spoils_dog?: true>,
#<Owner:0x00007fcc94337e68 id: 9, name: "Emily Elizabeth", spoils_dog?: true>]

Посмотрите на это, мы получили краткий обзор всего с помощью одной простой команды. Довольно удобно, но что, если мы хотим уточнить? Ну, мы, конечно, можем. Допустим, мы хотели найти только один конкретный экземпляр собаки или владельца. Если мы просмотрим экземпляры, которые мы только что прочитали с помощью .all, мы сможем найти идентификатор, связанный с каждым. В следующем примере давайте найдем экземпляр собаки, связанный с идентификатором 8. Для этого мы будем использовать метод .find. Этот метод принимает в качестве аргумента идентификатор и возвращает только один экземпляр.

#Method: .find(id)

[8] pry(main)> Dog.find(8)
D, [2022–12–07T18:47:19.577721 #34293] DEBUG - : Dog Load (2.2ms) SELECT "dogs".* FROM "dogs" WHERE "dogs"."id" = ? LIMIT ? [["id", 8], ["LIMIT", 1]]
=> #<Dog:0x00007fcc941e7220 id: 8, name: "Lucy", age: 3, breed: "Boston Terrier", demeanor: "springy", owner_id: 7>

И вот оно. Мы можем найти определенные экземпляры в наших таблицах, просто используя метод .find, передавая идентификатор в качестве аргумента. Еще один метод, очень похожий, но позволяющий передавать другие атрибуты, помимо идентификатора, — это метод .find_by. Одна ключевая вещь, которую следует помнить об этом методе, заключается в том, что он вернет только ПЕРВОЕ совпадение, которое он встретит. Это означает, что если вы выполняете поиск по атрибуту и ​​значению, которые есть у многих других экземпляров, как только он найдет первый, его работа будет выполнена. Давайте попробуем этот метод на классе собак и передадим атрибут «поведение» и значение «упрямый».

#Method: .find_by(attribute: value)

[9] pry(main)> Dog.find_by(demeanor: "stubborn")
D, [2022–12–07T18:52:38.869797 #34293] DEBUG - : Dog Load (1.1ms) SELECT "dogs".* FROM "dogs" WHERE "dogs"."demeanor" = ? LIMIT ? [["demeanor", "stubborn"], ["LIMIT", 1]]
=> #<Dog:0x00007fcc94107da0 id: 4, name: "Daisy", age: 3, breed: "French Bulldog", demeanor: "stubborn", owner_id: 2>

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

Следующий набор методов, о которых я расскажу, в чем-то похож. Каждый из них захватывает конкретный экземпляр, относящийся к позиции в таблице, в которой он находится. Это методы .first, .second, . Third, .fourth, .fifth и .last. Это отличные методы для быстрого захвата экземпляра или даже проверки последнего экземпляра, добавленного в определенную таблицу в базе данных. Давайте продемонстрируем их на таблице Owners.

#Methods: .first | .second | .third | .fourth | .fifth | .last

[10] pry(main)> Owner.first
D, [2022–12–07T19:01:00.853057 #34293] DEBUG - : Owner Load (1.0ms) SELECT "owners".* FROM "owners" ORDER BY "owners"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<Owner:0x00007fcc979c5d40 id: 1, name: "Kyle", spoils_dog?: false>

[11] pry(main)> Owner.second
D, [2022–12–07T19:01:12.063015 #34293] DEBUG - : Owner Load (0.4ms) SELECT "owners".* FROM "owners" ORDER BY "owners"."id" ASC LIMIT ? OFFSET ? [["LIMIT", 1], ["OFFSET", 1]]
=> #<Owner:0x00007fcc98195d68 id: 2, name: "Stephanie", spoils_dog?: true>

[12] pry(main)> Owner.third
D, [2022–12–07T19:01:16.713260 #34293] DEBUG - : Owner Load (0.1ms) SELECT "owners".* FROM "owners" ORDER BY "owners"."id" ASC LIMIT ? OFFSET ? [["LIMIT", 1], ["OFFSET", 2]]
=> #<Owner:0x00007fcc944fdbf8 id: 3, name: "Jared", spoils_dog?: true>

[13] pry(main)> Owner.fourth
D, [2022–12–07T19:01:21.072589 #34293] DEBUG - : Owner Load (0.2ms) SELECT "owners".* FROM "owners" ORDER BY "owners"."id" ASC LIMIT ? OFFSET ? [["LIMIT", 1], ["OFFSET", 3]]
=> #<Owner:0x00007fcc944a4710 id: 4, name: "Tami", spoils_dog?: false>

[14] pry(main)> Owner.fifth
D, [2022–12–07T19:01:24.954246 #34293] DEBUG - : Owner Load (0.2ms) SELECT "owners".* FROM "owners" ORDER BY "owners"."id" ASC LIMIT ? OFFSET ? [["LIMIT", 1], ["OFFSET", 4]]
=> #<Owner:0x00007fcc94446318 id: 5, name: "Beth", spoils_dog?: false>

[15] pry(main)> Owner.last
D, [2022–12–07T19:01:31.581836 #34293] DEBUG - : Owner Load (0.2ms) SELECT "owners".* FROM "owners" ORDER BY "owners"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<Owner:0x00007fcc943ed5b0 id: 9, name: "Emily Elizabeth", spoils_dog?: true>

Как и ожидалось, для методов с .first по .fifth мы читаем экземпляры с первого по пятый в таблице, а для метода .last мы читаем последний экземпляр. В этот момент вам может быть интересно: «Можете ли вы просто просмотреть таблицу поиска экземпляров по их соответствующим местам?» К сожалению, нет. Это все конкретные методы, основанные на индексах. Однако, чтобы еще раз проверить нашу работу, давайте сравним наши результаты с нашей таблицей.

И при этом вы только что изучили некоторые основные методы чтения для обхода базы данных! Чтобы напомнить методы, которые мы использовали для чтения, вот краткое изложение:

ClassName.all -> reads all instances in associated table

ClassName.find(id) -> reads one instance, find by id

ClassName.find_by(attribute: value) -> finds by attribute, returns nil if nothing is found

ClassName.first -> finds first instance

ClassName.second -> finds second instance

ClassName.third -> finds third instance

ClassName.fourth -> finds fourth instance

ClassName.fifth -> finds fifth instance

ClassName.last -> finds last instance

Здесь мы сделаем перерыв, а в следующем сообщении в блоге мы рассмотрим обновление и удаление CRUD. А пока попрактикуйтесь в создании и чтении экземпляров в вашей базе данных Ruby!

https://www.encora.com/insights/how-does-ruby-on-rails-activerecord-work-behind-the-scene#:~:text=What%20is%20ActiveRecord%3F,actual%20changes% 20в%20базу данных%20.