0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

ActiveRecordをRailsじゃない環境で使う(2019年版)

はじめに

日次バッチをつくるときに「ActiveRecordを使ってデータベースの処理スクリプト書きたいんだけど、別にRails入れるほどじゃないなー」ってのがあったので、そのときにやり方を調べたのをメモっておきます。

途中、Ruby単体でActiveRecordを使うベストプラクティス
https://qiita.com/tsugitta/items/a778f26e5d88e3cd5ab1
をかなり参考にさせてもらいました。 @tsugitta さんどうもありがとうございます!

環境つくる

壊れてもすぐにクリーンな環境に戻せるようにDockerで。

docker-composeで

docker-compose.yml
version: '3'
services:
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: hogehoge
    volumes:
      - mysql-data:/var/lib/mysql

  workspace:
    image: ruby:2.6
    stdin_open: true
    tty: true
    environment:
      MYSQL_HOST: mysql
      MYSQL_PASSWORD: hogehoge
    depends_on:
      - mysql
    links:
      - mysql
    volumes:
      - .:/usr/src/app
    working_dir: /usr/src/app

volumes:
  mysql-data:
    driver: local
$ docker-compose pull
$ docker-compose run --rm workspace bash
Starting ar_without_rails_mysql_1 ... done
root@30179237c853:/usr/src/app# irb
irb(main):001:0> 

—rm オプションを付けているので、bashを抜けるとコンテナは破棄される。常にクリーンな環境でRubyを実行できるぞ。

bin/console を用意しておく

最初は irb コマンドでいいんだけど、後々いろいろrequireするようになると面倒になってくるので、作っておく。

bin/console
#!/usr/bin/env ruby

require "bundler/setup"

require "irb"
IRB.start(__FILE__)

bundle gem したときにできるやつから使わない部分を削ぎ落としただけ。

root@5f20d34ccd2c:/usr/src/app# bin/console
irb(main):001:0> 

db:create, db:migrateできるようにするぞ

Gemfileに

gem 'activerecord'
gem 'mysql2'
gem 'rake'

を追記して、 bundle install したあと

Rakefile
require 'bundler/setup'
require 'active_record'
require 'erb'

include ActiveRecord::Tasks

root_dir = File.dirname(__FILE__)

config_database_yml_file = File.join(root_dir, 'config', 'database.yml')
config_database_yml = YAML::load(ERB.new(File.read(config_database_yml_file)).result)

DatabaseTasks.env = 'development'
DatabaseTasks.db_dir = File.join(root_dir, 'db')
DatabaseTasks.database_configuration = config_database_yml
DatabaseTasks.migrations_paths = File.join(root_dir, 'db', 'migrate')

task :environment do
  ActiveRecord::Base.configurations = config_database_yml
  ActiveRecord::Base.establish_connection :development
end

load 'active_record/railties/databases.rake'

こんなかんじのRakefileを用意すればよい。

降って湧いてきたようなこのRakefileはRuby単体でActiveRecordを使うベストプラクティスをかなり参考にさせてもらってます。ありがとうございます。

rake db:create してみる

config/database.yml
development:
  adapter: mysql2
  encoding: utf8mb4
  database: ar_without_rails_playground
  pool: 5
  username: root
  password: <%= ENV['MYSQL_PASSWORD'] %>
  host: <%= ENV['MYSQL_HOST'] %>

を用意して、

root@5f20d34ccd2c:/usr/src/app# bundle exec rake db:create
Created database 'ar_without_rails_playground'

いけたかも。

db:migrate してみる

rails g migration はできないので、マイグレーションファイルは気合で作る。

root@5f20d34ccd2c:/usr/src/app# mkdir -p db/migrate
root@5f20d34ccd2c:/usr/src/app# touch db/migrate/`date '+%Y%m%d%H%M%S'`_create_todo_item.rb  
root@5f20d34ccd2c:/usr/src/app# ls db/migrate/
20190712123300_create_todo_item.rb
db/migrate/20190712123300_create_todo_item.rb
class CreateTodoItem < ActiveRecord::Migration[5.2]
  def up
    create_table :todo_items do |t|
      t.string :title, null: false
      t.text :description, null: true
      t.datetime :created_at, null: false, index: true
    end
  end

  def down
    drop_table :todo_items
  end
end
root@5f20d34ccd2c:/usr/src/app# bundle exec rake db:migrate
== 20190712123300 CreateTodoItem: migrating ===================================
-- create_table(:todo_items)
   -> 0.0064s
== 20190712123300 CreateTodoItem: migrated (0.0065s) ==========================

よしできた。

ちなみに完全に蛇足だが、 db:rollback も無事にできるようになっている。

root@5f20d34ccd2c:/usr/src/app# bundle exec rake db:rollback
== 20190712123300 CreateTodoItem: reverting ===================================
-- drop_table(:todo_items)
   -> 0.0042s
== 20190712123300 CreateTodoItem: reverted (0.0044s) ==========================

モデルを作る

フォルダ構成はRailsおなじみの app/models にしておく。

app/models/todo_item.rb
class TodoItem < ActiveRecord::Base
end

ただ、Railsじゃないので、モデルのオートロードがされない。

root@5f20d34ccd2c:/usr/src/app# bin/console
irb(main):001:0> TodoItem.count
Traceback (most recent call last):
        2: from bin/console:6:in `<main>'
        1: from (irb):1
NameError (uninitialized constant TodoItem)

なので、適当にモデルクラスの初期化&ロード処理を config/init.rb とかで書いて、

config/init.rb
require 'active_record'
require 'active_support/time'
require 'erb'

config_dir = File.dirname(__FILE__)
config_database_yml_file = File.join(config_dir, 'database.yml')
config_database_yml = YAML::load(ERB.new(File.read(config_database_yml_file)).result)


# データベース接続の定義. created_at などで ActiveSupport::TimeWithZone を返してもらうための設定。
Time.zone = 'Tokyo'
ActiveRecord::Base.establish_connection(config_database_yml['development'])
ActiveRecord::Base.time_zone_aware_attributes = true

# モデルのクラスをrequireしておく
project_root_dir = File.expand_path("..", config_dir)
model_files = File.join(project_root_dir, 'app', 'models', '**', '*.rb')
Dir.glob(model_files).map do |model_file|
  require model_file
end

bin/consoleでrequireする。

bin/consoleに追記
diff --git a/bin/console b/bin/console
index 320047a..7ca01de 100755
--- a/bin/console
+++ b/bin/console
@@ -1,6 +1,7 @@
 #!/usr/bin/env ruby

 require "bundler/setup"
+require "./config/init.rb"

 require "irb"
 IRB.start(__FILE__)

そうすれば

root@5f20d34ccd2c:/usr/src/app# bin/console
irb(main):001:0> TodoItem
=> TodoItem (call 'TodoItem.connection' to establish a connection)
irb(main):002:0> TodoItem.count
=> 0
irb(main):003:0> TodoItem.create!(title: 'ActiveRecord6')
=> #<TodoItem id: 1, title: "ActiveRecord6", description: "", created_at: "2019-07-12 12:55:16">
irb(main):004:0> TodoItem.count
=> 1
irb(main):005:0> TodoItem.last.update!(description: 'bump activerecord from 5 to 6')
=> true
irb(main):006:0> TodoItem.last
=> #<TodoItem id: 1, title: "ActiveRecord6", description: "bump activerecord from 5 to 6", created_at: "2019-07-12 12:55:16">

いけたいけた。

スクリプトを実行してみる

適当な集計処理を書いたスクリプトを実行してみる。

scripts/aggregate_todo_count_by_date.rb
require './config/init.rb'

TodoItem.group("DATE_FORMAT(created_at, '%Y-%m-%d')").count.each do |created_at, count|
  puts " #{created_at}: #{count}"
end

puts '-' * 30

puts "      Total: #{TodoItem.count}"
root@5f20d34ccd2c:/usr/src/app# ruby scripts/aggregate_todo_count_by_date.rb 
 2019-07-09: 12
 2019-07-10: 11
 2019-07-11: 5
 2019-07-12: 79
------------------------------
      Total: 107

いけたいけた。

まとめ

作るべきファイルはたくさんあったけど、だいたい固定の内容なので、実は簡単かも?

ActiveRecord 6で複数DB接続とかできるようになるので、そこも時間あったら調べてみようかな。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
0
Help us understand the problem. What are the problem?