Покорим Ruby вместе! Капля тринадцатая

    Сегодня мы познакомимся с таким мощным и полезным инструментом Руби и компонентом RoR, как Active Record. Как часть RoR мы знаем Active Record из первого шага статей о рельсах (что-то эта серия не очень хорошо пошла, правда). Рассмотрим практическое применение компонента в Руби, что очень пригодится и в RoR.

    Active Record придерживается принципов ORM: базы данных могут быть представлены в виде объектно-ориентированного кода, где таблицы — это классы, строки таблицы — объекты, а поля — атрибуты объекта. Однако Active Record отличается от других ORM библиотек, например, от Hibernate из Java, тем, что здесь нет необходимости начальной конфигурации — все работает «из коробки». Active Record написан на чистом Руби, что позволяет нам работать с ним, как с обычным классом: использовать наследование, переписывать методы, использовать метапрограмминг и др. Да, атрибуты объекта в большинстве случаев заполняются с помощью SQL-запросов и точно также атрибуты записываются назад в таблицу. Но все, что происходит между этими двумя процессами — обычная работа с объектом Руби. Стоит понимать «отдельность» существования записей базы данных и объектов Руби: подчас база данных будет отличаться от класса Руби.

    Когда вы создаете новую запись в базу, обновляете существующую или просто считываете данные, все проиходит в три шага: создание объекта Active Record, доступ и манипулирование атрибутами объекта и сохренение атрибутов в виде записей в БД.

    Установка Active Record


    Как и обычно в Руби, установка — плевое дело, достаточно установить необходимый джем: gem install activerecord. Подключение к базе данных происходит с помощью адаптеров, большинство которых уже встроены (DB2, Firebird, FrontBase, MySQL, Open-Base, Oracle, PostgreSQL, SQLite, SQL Server и Sybase), необходимо только передать параметры подключения к БД. Также следует убедиться, что у вас установлен/запущен сервер БД и установлен нужный джем (mysql или sqlite3-ruby, например).

    In use



    Начнем с такой таблицы Accounts:
    id (integer; auto-incremented; primary key)
    username (text)
    password (text)


    В первую очередь подключаемся к БД:

    require "rubygems"
    require "activerecord"

    ActiveRecord::Base.establish_connection(
            :adapter => "mysql",
            :host => "localhost",
            :username => "user",
            :password => "pass",
            :database => "project_development")



    Как видим, для этого надо передать методу establish_connection параметры в виде хэша. Для SQLite нужны только :adapter => "sqlite" и :database.

    В соответствие с таблицей создаем класс-наследник:

    Class Account < ActiveRecord::Base
    end



    Здесь стоит обратить особое внимание на название класса. Как обычно, здесь работает принцип conventions over configuration, по которым название таблицы во множественном числе, название класса — в единственном. Можно, конечно, обходить это ограничение, но делать этого не стоит — работайте в Ruby/Rails way, и вы поймете, насколько это удобно.

    Итак, как мы знаем, чтобы создать запись в таблице, мы должны работать с объектами Руби. Для создания новой строки в БД Accounts, создаем экземпляр класса Account, используя стандартный метод new:

    acc1 = Account.new
    acc1.username = "maxelc"
    acc1.password = "maxspassword"



    Здесь как раз мы видим одно из ключевых преимуществ Руби — метапрограмминг. Налету для нас были созданы методы, одноименные со столбцами таблицы, для редактирования атрибутов экземпляра. Очень удобно, но это только начало, дальше — больше. Однако это только лишь экземляр класса, таблица еще не изменилась (о чем говорилось выше). Дла того, чтобы «синхронизировать» экземляр с таблицей, создав новую строку, используем метод save:

    acc1.save


    Другой способ внесения записи:

    acc2 = Account.new(
        :username => "hbuser",
        :password => "habrapass")
    acc2.save



    Записи сохранять мы научились, теперь нужно их извлечь. Понимаем, что при этом мы должны создать экземпляр класса и заполнить его атрибутами из строки таблицы. На деле это проще. Если мы знаем id строки, то используем встроенный (core) метод find:

    result1 = Account.find(1)



    Довольно тривиально, а что, если мы хотим искать по столбцам/атрибутам? Тут метапрограммная основа Руби опять приходит нам на помощь — используем динамические методы поиска:

    result2 = Account.find_by_username("hbuser")


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

    result3 = Account.find_or_create_by_username("superuser")


    И в этом случае название метода говорит само за себя: «найди или создай username». Соответственно, если при поиске элемент (говорим элемент — подразумеваем строку таблицы, но, на самом деле, это), попадающий под условие поиска не будет найден, то он будет создан и сохранен в таблицу (save не нужен в данном случае).

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

    Account.update(1, {:password => 'newpass'})
    puts "запись сохранена"
    Account.delete(2)



    Здесь стоит отметить, что несмотря на то, что мы удалили строку из БД, экземпляр продолжает существовать read-only, пока непосредственно не будет удален сам экземпляр или не выйдет за пределы области видимости (scope — отдельная тема).

    Связи Active Record



    Связь между объектами — это не только невероятно важная часть функциональности Active Record, это просто необходимо в реальных приложениях. Существует несколько видов связей, мы рассмотрим один.

    Теперь мы хотим к каждому пользователю привязать определенную группу, как то Администратор или Гость. Начнем с того, что создадим таблицу Groups, содержащую id и name (группы). Также нужно добавить в таблицу Accounts новый столбец group_id для использования в качестве внешнего ключа (опять работают договоренности об именовании). В нашем случае можно представить связь как «каждый аккаунт принадлежит какой-то роли». Для такого вида связей используется метод belongs_to. Но сначала создадим новый класс для Groups в нашем скрипте:

    class Group < ActiveRecord::Base
    end



    Связь же мы должны определить в классе Account, дописав класс так:

    class Account < ActiveRecord::Base
    belongs_to :group
    end



    Теперь у нас есть односторонняя связь — аккаунт знает, что у него есть группа, а группа не знает об ее аккаунтах. Давайте создадим группу администраторов и назначим в нее аккаунт:

    admin_group = Group.find_or_create_by_name("admin")
    account.group = admin_group
    account.save



    Как видим, для класса Account появился новый метод, записывающий ему группу. Мы просто «приравняли» атрибут (на самом деле не атрибут, но для конечного пользователя это выглядит так) аккаунта ко всему экземпляру группы, Active Record понимает это, делая то, что нам нужно.

    Эпилог



    Вот такое введение в эту мощную систему. Прежде всего, она, конечно, будет использоваться при работе с web-приложениями, Rails, в частности. Если у вас есть желание, то мы можем дальше углубиться в Active Record или, например, Rake. Как обычно — комментарии и замечания приветствуются на самом высоком уровне ;)
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 27

      +1
      Спасибо.
      Жду продолжения про has_many, has_and_belongs :)

      А отдельно от Рельсов в ActiveRecord можно использовать миграции? (хотя это скорее к rake относится)
        0
        Да, можно, конечно, обязательно напишу. Спасибо, что хоть кто-то отписался :))) Вроде старался, да и тема интересная…
          +3
          Это просто вечер воскресения, все приходят в себя после выходных и праздников :) Тема очень интересная, можно потом на википедию в раздел книги опубликовать.
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Точно, исправляю!
          0
          Вообще вся серия супер!!! Очень последовательно. Надеюсь в последствии получится большой учебник, в котором получится отразить полностью создание web-приложения. И главное про цикл разработки написать отдельную статью. Мне кажется это важно.
            0
            Спасибо за труд. Статья хорошая, но, честно говоря, совершенно неинтересная. Все, что вы рассказали — это общие места, которые разжеваны в десятках книг/статей/туториалов. Заметка концептуально не вписывается в ваш цикл. Ведь Ruby это не только Рельсы и непонятно при чем здесь ActiveRecord. Хотелось бы все-таки увидеть больше материалов именно по языку, RoR в рунете и так хорошо освещен. Есть же много интересных тем: применение Ruby вне веба, всякие тонкости и неочевидные вещи. Вот например интересная заметка о блоках, лямбдах и процедурных объектах — www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas Уверен, что изложенный там материал понятен далеко не всем. Такие статьи сложнее писать, но и пользы от них несоизмеримо больше, чем от очередного введения в AC.
            Ну это так, фидбэк от читателей, не воспринимайте мои слова в штыки.
              0
              Вам спасибо за отзыв. На самом деле просто хочется излагать все последовательно и доступно для начинающих. Надеюсь когда-то настанет время, и мы будем писать и более серьезные вещи :) А вообще — беру на заметку. Еще раз спасибо!
              0
              result1 = Account.find(1)
              acc2.save

              save тут явно лишний.

              Простенько так, самые азы.
                0
                Затесалось :-[ Попробуем окунуться глубже в следующей статье
                0
                > account1 = Account.find(1)
                > account1.password = «newpass»
                > account1.save

                Вы считаете, что эффективно перед выполнением UPDATE делать лишний SELECT? И вообще, что хорошего в ActiveRecord, нагенерирована куча методов (вроде find_by_*, и тд), большинство из которых так нигде и не используется. В чем смысл?

                И еще, вопросик: а что, если надо найти записи по какому-то условию, например, все юзеры, зарегистрированные до такого-то числа, и принадлежащие к определенной группе? А если надо выбрать список групп и количество пользователей в каждой? Сомневаюсь, что для этого найдется готовый метод.
                  0
                  Действительно неэффективно. Для этого есть метод update:

                  Account.update(1, {:password => 'newpass'})


                  А насчет условий — об этом мы еще поговорим. Готовых, конечно, не найдется
                    +1
                    Вот про всякие сложные условия мне как раз и интересно узнать))
                      +1
                      Договорились ;)
                  • НЛО прилетело и опубликовало эту надпись здесь
                      +1
                      методы типа find_by_* заранее не генерятся. ActiveRecord отлавливает ошибку метод не наден, переопределяя method_missing. Затем объявляет этот метод и выполняет.
                        0
                        > нагенерирована куча методов (вроде find_by_*, и тд), большинство из которых так нигде и не используется. В чем смысл?

                        Смысл — в усилении абстракции, приближении к более человеческому языку. На самом деле, внутри этих методов будет все тот же вызов find(..., conditions => [...]) (если более точно — find_every(...)), так что, find_by_* — всего лишь синтаксический сахар. При этом первый вызов будет медленней последующих, т.к. метод будет динамически создан по method_missing при первом вызове find_by_*.
                          0
                          Ну если они сами генерируются, то да, кому-то наверно это может быть удобно) Хотя я бы не поленился пистаь find_by( :id => 6 ) (или как там это у вас в Руби пишется), по моему так тоже неплохо.
                        0
                        Я так полагаю, что и в AR STI реализован на varchar стыках?
                          0
                          Интересно. Первая действительно понятная статья на эту тему, а то книжки какие-то тупые пошли...)
                            0
                            ar.rubyonrails.com/ — седня утром почитал — да материала много но не сказал бы, что непоянтнло. Просто надо знать про MVC/ORM непонаслышке, и понимать зачем они сделаны.
                              0
                              MVC знаю непонаслышке, а вот в ORM только ещё въезжаю. Спасибо за наводку.
                            0
                            Поставь пожалуйсто в методах скобочки acc1.save(), дико раздражает этот языковой беспредел, позволяющий вызывать метод как атрибут.
                            Ещё бы про схемы yaml рассказал.
                              0
                              > Поставь пожалуйсто в методах скобочки acc1.save(), дико раздражает этот языковой беспредел, позволяющий вызывать метод как атрибут.

                              Дело привычки/непривычки. А методы без скобок, наоборот — одна из возможностей приблизить язык к более человеческому-описательному. Например, «before_filter :a» или «attr_accessor :a, :b», или belongs_to :some_table — мы, как бы, описываем, а не «вызываем» методы (хотя на самом деле — это вызов метода).
                                0
                                это как прировнять глагол и существительное.
                                  0
                                  По мне так главное, чтобы разработчик понимал, что это метод (я как мог постарался донести это в статье). А после — только наслаждался «человеческим» кодом :)
                              –1
                              Вот я имею полное право обьективно откомментить и оценить этот блог, так как только 2 недели назад познакомился с ruby в частности и с программированием в целом. И, честно говоря, я запнулся уже капле на 3 или 4. Хотя, наверное, опытным в программировании людям все понятно интуитивно.

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

                              Самое читаемое