1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

はじめに

日次バッチをつくるときに「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接続とかできるようになるので、そこも時間あったら調べてみようかな。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?