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

    Библиотеку для работы с базами данных ActiveRecord можно использовать и вне Rails.

    Задача

    Наш тестовый проект будет брать время восхода луны с Яндекс.Погода и сохранять ее в базу данных, используя ActiveRecord. Заодно посмотрим на работу с Nokogiri (хорошую замену hpricot).

    Структура проекта

    Мы будем использовать миграции, файл конфигураций database.yaml, Rakefile, базу данных sqlite (development.sqlite3), в общем, окружение приближенное к любимым рельсам.

    Посмотреть на github.

    image

    Настройка соединения с БД и создание таблицы с помощью миграций

    Для удобства конфигурация соединения с БД вынесена в отдельный файл lib/config/database.yml:
    1. adapter: sqlite3
    2. database: lib/db/development.sqlite3
    А соединение с БД в lib/config/environment.rb:
    1. require 'rubygems'
    2. require 'active_record'
    3. require 'yaml'
    4.  
    5. # Загружаем файл настройки соединения с БД
    6. dbconfig = YAML::load(File.open(File.join(File.dirname(__FILE__), 'database.yml')))
    7.  
    8. # Ошибки работы с БД направим в стандартный поток (консоль)
    9. ActiveRecord::Base.logger = Logger.new(STDERR) # Simple logging utility. logger.rb -- standart lib
    10.  
    11. # Соединяемся с БД
    12. ActiveRecord::Base.establish_connection(dbconfig)

    Подготовим миграцию БД для создания таблицы. Для простоты в ней всего два строковых поля: day и time:
    1. class CreateMoonRiseTimes < ActiveRecord::Migration
    2.   def self.up
    3.     create_table :moon_rise_times do |t|
    4.       t.string :day
    5.       t.string :time
    6.     end
    7.   end
    8.  
    9.   def self.down
    10.     drop_table :moon_rise_times
    11.   end
    12. end
    Для выполнения миграции создадим rake-файл.
    1. require 'lib/config/environment.rb'
    2.  
    3. # Документация по rake docs.rubyrake.org
    4. # namespace -- rake.rubyforge.org/classes/Rake/NameSpace.html
    5.  
    6. namespace :db do
    7.   desc "Migrate the database"
    8.   task :migrate do
    9.     # выполнение всех миграций из lib/db/migrate,
    10.     # метод принимает параметры: migrate(migrations_path, target_version = nil)
    11.     # в нашем случае
    12.     # migrations_path = lib/db/migrate
    13.     # target_version =  ENV["VERSION"] ? ENV["VERSION"].to_i : nil
    14.     # миграция запускается как rake db:migrate VERSION=номер_версии
    15.     ActiveRecord::Migrator.migrate('lib/db/migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil )
    16.   end
    17. end


    Попробуем мигрировать:
    1. $ rake db:migrate VERSION=1

    2. (in /home/data/projects/ActiveRecord-without-Rails)
    3. ==  CreateMoonRiseTimes: migrating ============================================
    4. -- create_table(:moon_rise_times)
    5.   SQL (1.0ms)   CREATE TABLE "moon_rise_times" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "day" varchar(255), "time" varchar(255))
    6.    -> 0.0019s
    7. ==  CreateMoonRiseTimes: migrated (0.0020s) ===================================
    8.   SQL (0.1ms)   INSERT INTO schema_migrations (version) VALUES ('1')


    Отлично. Миграция 001_create_moon_rise_times.rb создала таблицу moon_rise_times с полями day и time.

    Извлечение данных и сохранение их в БД

    Теперь надо найти какие-нибудь полезные данные для сохранения в базу данных. Для примера будем брать с сайта Яндекс.Погода время восхода Луны. Также можно вылавливать курсы валют, новости, значение кармы, etc...

    Для начала создадим тестовый файл lib/test_search.rb ищущий нужные данные на Яндекс.Погода:
    1. # Файл с небольшим скприптом для тестирования работы nokogiri.
    2.  
    3. $KCODE = "u"
    4. require 'jcode'
    5. require 'rubygems'
    6. require 'open-uri'
    7. require 'iconv'
    8. require 'nokogiri'
    9.  
    10.  
    11. # Загрузим html-документ
    12. source = open("pogoda.yandex.ru/26063/details/")
    13.  
    14. # Создадим объект nokogiri из загруженного документа
    15. data = Nokogiri::HTML(source)
    16. # Извлечем все строки html-таблицы с помощью метода search и простого xpath-выражения
    17. # wiki.github.com/tenderlove/nokogiri
    18.  
    19.  
    20. =begin
    21. Фрагмент исходного html-файла, в котором надо найти дату восхода Луны :)
    22.  
    23. <th class="date" rowspan="4"><b title="">9</b> <span>июля</span></th>
    24.  
    25. =end
    26.  
    27. data_html = data.search("th[@class = date]//b").first.inner_html + " " + data.search("th[@class = date]//span").first.inner_html
    28. puts data_html
    29.  
    30. =begin
    31. Фрагмент исходного html-файла, в котором надо найти время восхода Луны в день, определенный выше :)
    32.  
    33. <td class="dawn-dark" rowspan="4">
    34.     <dl>
    35.        <dt>Восход</dt>
    36.        <dd>04:52</dd>
    37.        <dt>Заход</dt>
    38.        <dd>23:15</dd>
    39.     </dl>
    40. <img src="//i.yandex.st/weather/i/moon/07.gif" alt="Убывающая луна" title="Убывающая луна">
    41. </td>
    42.  
    43. =end
    44.  
    45. time_html = data.search("td[@class = dawn-dark]//dl//dd").first.inner_html
    46. puts time_html


    Получаем:
    1. $ ruby lib/test_search.rb
    2. 10 июля
    3. 04:53

    Ок, то, что нам нужно.
    Теперь надо сохранить полученные данные в БД. Для этого в ActiveRecord::Base есть метод create

    Файл, выполняющий главную работу, lib/main.rb:
    1. $KCODE = "u"
    2. require 'jcode'
    3. require 'rubygems'
    4. require 'active_record'
    5. require 'yaml'
    6. require 'logger'
    7. require 'open-uri'
    8. require 'iconv'
    9. require 'nokogiri'
    10.  
    11.  
    12. # загружаем файл environment.rb настройки и соединение с БД
    13. require File.join(File.dirname(__FILE__), 'config/environment.rb')
    14.  
    15. # Создаем класс MoonRiseTime, обертывающий таблицу moon_rise_times
    16. # Ячейки таблицы: day -- тип data, rise_times -- тип time
    17. #
    18.  
    19. class MoonRiseTime < ActiveRecord::Base
    20. end
    21.  
    22. MoonRiseTime.create do |moon_rise_time|
    23.   # Загрузим html-документ
    24.   source = open("pogoda.yandex.ru/26063/details/")
    25.  
    26.   # Создадим объект nokogiri из загруженного документа
    27.   data = Nokogiri::HTML(source)
    28.   # Извлечем все строки html-таблицы с помощью метода search и простого xpath-выражения
    29.   # nokogiri.org/Nokogiri/XML/Node.html#method-i-inner_html
    30.  
    31.  # :TODO:  Привести типы данных к data и time
    32.  # Находим в исходном html дату
    33.   data_moon_rise = data.search("th[@class = date]//b").first.inner_html
    34.   month_moon_rise = data.search("th[@class = date]//span").first.inner_html
    35.  
    36. # передаем найденные данные методам класса-обертки БД
    37.   moon_rise_time.day = data_moon_rise + " " + month_moon_rise
    38.  
    39.   moon_rise_time.time = data.search("td[@class = dawn-dark]//dl//dd").first.inner_html
    40. end

    Запустим его:
    1. ruby lib/main.rb
    2.   MoonRiseTime Create (0.4ms)   INSERT INTO "moon_rise_times" ("time", "day") VALUES('04:53', '10 июля')


    Вроде все хорошо.

    Посмотрим, что сохранилось в базе с помощью небольшого скрипта lib/test_fetch_data.rb:
    1. # Файл с небольшим скприптом для тестирования
    2. # извлечения данных из таблицы moon_rise_times.
    3.  
    4. # загружаем файл настройки и соединеняемся с БД в файле environment.rb
    5. require File.join(File.dirname(__FILE__), 'config/environment.rb')
    6.  
    7. # Создаем класс MoonRiseTime, обертывающий таблицу moon_rise_times
    8. # Ячейки таблицы: day, time -- тип string
    9.  
    10. class MoonRiseTime < ActiveRecord::Base
    11. end
    12.  
    13. # Извлекаем все записи из БД в fetch_result
    14. fetch_result = MoonRiseTime.all
    15.  
    16. # Выводим на экран fetch_result
    17. fetch_result.each do |result_item|
    18.   puts result_item.id
    19.   puts result_item.time
    20.   puts result_item.day
    21. end

    Вывод:
    1. $ ruby lib/test_fetch_data.rb
    2.   MoonRiseTime Load (0.5ms)   SELECT * FROM "moon_rise_times"
    3. 1
    4. 04:53
    5. 10 июля

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

    Использованные материалы

    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 16
    • 0
      Жду ваших исправлений, замечаний, дополнений.
      • 0
        Вот лень читать, прости, но АктивРекорд можно использовать просто как гем. Поэтому, мне кажется, достаточно использовать require 'active_record' для использования библиотеки (гема) и все.

        Так, по крайней мере, я использую активрекорд в Синатре.
      • +1
        А почему всё: и конфиги, и исполняемые файлы — лежит в lib?
        Я бы запуск функционала оформил посредством rake-задачи.
        • 0
          Я в Netbeans делал проект, там весь программный код лежит в lib. Можно и rake-задачу сделать, распарсить какой-нибудь каталог например.
          • +5
            Я считаю, что в таких поделках стоит сохранять структуру rails. Удобнее другим разработчикам разбираться в проекте, а также редакторы не будут сбиваться с толку. Например, vim всё так же будет хорошо понимать команды :Rlib, :Rtask и прочее.

            ps. Перенесите пост в ruby?
            • 0
              Перенес.
              Я считаю, что в таких поделках стоит сохранять структуру rails.

              Ок, сделаю. Спасибо за отзыв :)
            • 0
              Не обязательно, можно создать и другие каталоги и добавить их в проект.
          • +4
            Небольшое отсутпление от темы: можно напрямую же брать XML с Яндекcной Погоды и парсить HTML не надо!
            • 0
              Согласен с комментом выше по поводу то что не надо конфиги и миграции в lib пихать.
              Насколько понимаю в lib/main.rb не надо делать require 'active_record', он уже в enviroment есть. И require 'logger' лучше туда же перетащить. Еще бы неплохо все-таки модель вынести в отдельный файл, ну и вообще классами все оформить. Но это уже занудство, понимаю что тут не это показывается.

              А материал полезный. Так то было понятно что active_record можно использовать без rails но вот как все это дело проинициализировать и сконфигурировать, этого не знал. Спасибо.
              • 0
                На github.com/mikhailian/tm4r у меня есть масенький проект, использующий ActiveRecord без Rails. Там структура файлов в проекте более соответствует традициям rubygems.
                • 0
                  Вам спасибо за отзыв. Я новичок в программировании, конструктивную критику очень жду.
                • +1
                  без рельс я бы использовал DataMapper (www.datamapper.org)

                  там имеется автоматическая подгрузка ассоциаций, да и синтаксис поприятнее:
                  # 'gt' means greater-than. We also do 'lt'.
                  Person.all(:age.gt => 30)

                  # 'gte' means greather-than-or-equal-to. We also do 'lte'.
                  Person.all(:age.gte => 30)

                  # 'not' allows you to match all people without the name "bob"
                  Person.all(:name.not => 'bob')

                  # If the value of a pair is an Array, we do an IN-clause for you.
                  Person.all(:name.like => 'S%', :id => [ 1, 2, 3, 4, 5 ])

                  # Does a NOT IN () clause for you.
                  Person.all(:name.not => [ 'bob', 'rick', 'steve' ])
                  • 0
                    а в MongoDB вообще не нужны миграции и схемы :)
                    ээм кажется человек пришел из С, С++ сферы, main.rb файл :)
                    Вообще сейчас, а может и раньше, применяется классовый и модульные подход. То есть оформляем классы, модули, в главном файле подгружаем их через autoload и используем все в главном файле, что избавит нас от написания простыней require '' в каждом файле… that's ruby way :)
                    • +1
                      Netbeans создает файл main.rb. А для демонстрации применения, я думаю, не надо создавать кучу классов и модулей =)
                    • 0
                      Есть еще как минимум дин метод конфигурирования связи с БД, более простой, нежели приведенный:

                      ActiveRecord::Base.establish_connection(
                        :adapter => 'sqlite3',
                        :database => 'database.sqlite3'
                      )
                      
                      • 0
                        DataMapper имхо лучше для НЕрельсовых приложений. Пошустрее и проще будет.

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