ラベル Rails の投稿を表示しています。 すべての投稿を表示
ラベル Rails の投稿を表示しています。 すべての投稿を表示

2020年4月25日土曜日

Rails.cache を使って SQL クエリの結果をキャッシュしてみた

概要

前回 Postgres を使って Rails6 に入門しました
今回はそのアプリを使って Rails のクエリキャッシュ機能を使ってみたいと思います
簡単に言えば同じクエリは毎回データベースに問い合わせせずにメモリや Redis にキャッシュするという機能です
Rails のバージョンによって構文や使い方が異なるので注意してください

環境

  • macOS 10.15.4
  • Rails 6.0.2.2
  • Redis 5.0.5

キャッシュの有効化

  • rails dev:cache

キャッシュするクエリをモデルで定義する

  • vim app/models/my_task.rb
class MyTask < ApplicationRecord
  def self.cache_all
    Rails.cache.fetch("#{cache_versioning}/list", expires_in: 30.seconds) do
      MyTask.order(posted_date: "DESC").all.to_a
    end
  end
end

ポイントは最後に to_a を付けて配列として返している点です

コントローラでキャッシュを参照するメソッドを使う

  • vim app/controllers/my_tasks_controller.rb
class MyTasksController < ApplicationController
  protect_from_forgery

  def list
    @my_tasks = MyTask.cache_all
  end

  # 一部省略・・・
end

動作確認

  • rails s

で起動して localhost:3000/my_tasks にアクセスしてみましょう

  • c=0 && while :;do ((c ++)); echo $c; curl localhost:3000/my_tasks > /dev/null 2>&1; sleep 1; done

紹介のアクセスは SQL が発行されていますが 2 回目以降は 30 秒経過するまでは SQL が発行されていないのがログから確認できると思います

おまけ: redis をキャッシュにする方法

上記はメモリ上にキャッシュを持っており参照するのが大変です
そんな場合は redis を使いましょう

redis のインストール

  • vim Gemfile
gem "redis_rails"
  • bundle install

config.cache_store の変更

設定ファイルを変更してオンメモリだったキャッシュ先を redis に変更します
Rails6 では :redis_cache_store, { url: ENV['REDIS_URL'] }

  • vim config/environments/development.rb
    (一部省略)
if Rails.root.join('tmp', 'caching-dev.txt').exist?
  config.action_controller.perform_caching = true
  config.action_controller.enable_fragment_cache_logging = true

  # config.cache_store = :memory_store
  config.cache_store = :redis_cache_store, { url: "redis://192.168.100.1/0" }
  config.public_file_server.headers = {
    'Cache-Control' => "public, max-age=#{2.days.to_i}"
  }
else
  config.action_controller.perform_caching = false

  config.cache_store = :null_store
end

あとは redis を起動してアプリで確認すればキャッシュが redis に作成されていることが確認できると思います

192.168.100.1:6379> keys *
1) "true/list"
192.168.100.1:6379> TTL true/list
(integer) 19

データ自体は圧縮されていて平文では確認が難しい状態になっています
redis のエンドポイントに関する細かい設定は参考サイトにあるドキュメントを確認してください

最後に

Rails6 で Rails.cache を使ってみました
かなり簡単に使えました
リアルタイムでの表示が不要であったりデータがすぐに反映されなくても良いようなビューを扱う場合はかなり使える技だと思います

今回は SQL 文の発行をキャッシュしましたがビューの情報やネスト構造の一部だけをキャッシュするロシアンドールキャッシングなどもありいろいろな情報をキャッシュすることができます

参考サイト

2020年4月18日土曜日

Rails6 + Sidekiq 超入門

概要

前回 Rails6 と PostgreSQL を使って簡単な Web アプリを作ってみました
今回は Rails6 と Sidekiq を組み合わせて非同期な Web アプリを作ってみます

Redis のインストール

  • brew install redis
  • brew services start redis

Sidekiq のインストール

rails new で作成したアプリ配下にある Gemfile を編集します

  • vim Gemfile
gem "sidekiq"
  • bundle install

Sidekiq の初期化

config/initializers/sidekiq.rb を作成してここに Redis のエンドポイントなど Sidekiq の初期化に必要なパラメータなどを設定します

  • vim config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: 'redis://localhost:6379/0' }
end

Sidekiq.configure_client do |config|
  config.redis = { url: 'redis://localhost:6379/0' }
end

Worker の作成

app/workers 配下に作成します
デフォルトだとディレクトリが存在しないので作成します
ワーカーのファイル名は名前はワーカークラス名のスネークケースで作成します

  • mkdir app/workers
  • vim app/workers/wait_worker.rb
class WaitWorker
  include Sidekiq::Worker

  def perform(time, params)
    sleep time
    my_task = MyTask.new
    params = JSON.parse(params)
    my_task.title = params["title"]
    my_task.description = params["description"]
    my_task.public = params["public"]
    my_task.updated_date = Time.now
    if my_task.public
      my_task.posted_date = Time.now
    end
    my_task.save
  end
end

Sidekiq を経由すると params の Rails 側アプリとは少し異なる点に注意してください
後述していますがハッシュを JSON 文字列に変換してから Sidekiq に渡しています
やっていることは前回のタスクを登録する処理と同じです

エンキューする処理の実装

Rails アプリ側でエンキューするようにしましょう
コントローラを修正します

  • vim app/controllers/my_tasks_controller.rb
class MyTasksController < ApplicationController
  protect_from_forgery

  def list
    @my_tasks = MyTask.order(posted_date: "DESC").all
  end

  def show
    id = params[:id]
    @my_task = MyTask.find(id)
  end

  def create
    WaitWorker.perform_async(5, params.to_json)
    redirect_to '/my_tasks'
  end
end

1 つポイントがあり Sidekiq にハッシュ値を渡したい場合は一旦 JSON 文字列に変換する必要があります
そしてワーカー側で JSON 文字列からハッシュに再度変換します
今回は適当に遅延させたいので 5 秒ウェイトしてから DB に INSERT します

動作確認

まずはワーカーを起動します

  • bundle exec sidekiq

そして Rails アプリを起動しましょう

  • rails server

あとはタスクを作成してみましょう
5 秒ほど遅延があってからデータが挿入されるのが確認できると思います

最後に

Rails6 + Sidekiq を試してみました
Rails 上で使う場合はディレクトリ構成が決まっているのでその点だけ注意が必要かなと思います
Rails 上の場合は require 系が不要なのが記述が少なく済むので嬉しいのかなと思います

参考サイト

2020年4月17日金曜日

Rails6 + PostgreSQL 超入門

概要

備忘録として Rails6 + postgresql の簡単なチュートリアル記事を残しておきます
Rails を久しぶりに使うときの導入記事として使いたいと思っています
ただバージョンによってかなり挙動や必要なツールが異なるのでその辺りはバージョンに応じて適宜対応してください

環境

  • macOS 10.15.4
  • Ruby 2.7.1p83
  • Rails 6.0.2.2
  • Postgres 12.2
  • nodejs 13.12.0

事前準備

  • gem install rails

グローバルにインストールしました
本当は bundle init してローカルインストールにしたかったのですがうまく動作しなかったので辞めました

PostgreSQL のインストールと起動

Postgres がまだインストールされていない場合はインストールしましょう

  • brew install postgresql
  • pg_ctl -D /usr/local/var/postgres start

停止したい場合は stop すれば OK です

  • pg_ctl -D /usr/local/var/postgres stop

nodejs/yarn のインストール

なぜか内部的に nodejs も使っているのでインストールします
webpack などの関係かなと思いますがとりあえず素直に入れます

  • brew install node
  • npm install -g yarn

新規アプリ作成

お決まりの TODO アプリになります
Postgres を使ったアプリを作成するので -d postgresql を忘れないようにしましょう

  • rails new todo -d postgresql

大量の gem がインストールされるので待ちます
スモールスタートしたいのにこの大量の gem をインストールするのがあまり好きになれないところでもあります

データベース作成

以下は作成したアプリのディレクトリ内で行います

  • cd todo
  • rake db:create RAILS_ENV=development

これで todo_developmenttodo_test というデータベースが作成されます
psql -l コマンドなどで直接 Postgres に問い合わせても確認できます

データベースをマイグレート

  • rails generate migration create_my_tasks

これでマイグレーションファイル db/migrate/20200415012753_create_my_tasks.rb が作成されるので編集します

  • vim db/migrate/20200415012753_create_my_tasks.rb
class CreateMyTasks < ActiveRecord::Migration[6.0]
  def change
    create_table :my_tasks do |t|
      t.string :title
      t.text :description
      t.timestamp :posted_date
      t.timestamp :updated_date
      t.boolean :public
    end
  end
end
  • rake db:migrate RAILS_ENV=development

これで todo_development データベース内に my_tasks というテーブルが作成されました

% psql todo_development psql (12.2) Type "help" for help.   todo_development=# \d my_tasks Table "public.my_tasks" Column | Type | Collation | Nullable | Default ————–+—————————–+———–+———-+————————————– id | bigint | | not null | nextval('my_tasks_id_seq'::regclass) title | character varying | | | description | text | | | posted_date | timestamp without time zone | | | updated_date | timestamp without time zone | | | public | boolean | | | Indexes: "my_tasks_pkey" PRIMARY KEY, btree (id)

新規モデル作成

作成した my_tasks テーブルを扱うモデルを作成します
すでにマイグレーションファイルは作成しているので --skip を指定します
g model はマイグレーションファイルも作成してくれるようなので g migration を実行しないで g model だけ実行しても良いと思います

  • rails generate model my_task --skip

app/models/my_task.rb が作成されれば OK です
test/models/my_task_test.rbtest/fixtures/my_tasks.yml はテスト用のファイルになります
ちなみにモデルは単数形で DB のテーブル名は複数形になります

コントローラ作成

実際に Web アプリのハンドリングを行うコントローラを生成します
my_tasks を CRUD するためのコントローラを作成しましょう

  • rails g controller my_tasks

ggenerate の省略形になります
慣れたら省略形を使ったほうが楽です

app/controllers/my_tasks_controller.rbapp/views/my_tasksapp/helpers/my_tasks_helper.rb が主なファイルになります
あとはスタイルシート用の .scss ファイルとテスト用のファイルも作成してくれます

routes.rb でルーティングを設定する

どの URL にアクセスしたらどのメソッドを実行するか定義します
ようやくこの辺りからプログラミングっぽくなってきます

  • vim config/routes.rb
Rails.application.routes.draw do
  get 'my_tasks', to: 'my_tasks#list'
  get 'my_tasks/:id', to: 'my_tasks#show'
  post 'my_tasks', to: 'my_tasks#create'
end

とりあえず今回は簡単な「取得」と「作成」だけ実装します
大抵の「更新」や「削除」なども必要になると思うので余裕があれば実装してみましょう

コントローラにルーティングされるメソッドを実装する

とりあえず一番簡単そうな list を実装してみましょう

  • vim app/controllers/my_tasks_controller.rb
class MyTasksController < ApplicationController
  def list
    @my_tasks = MyTask.order(posted_date: "DESC").all
  end
end

MyTask はモデルになります
コントローラからモデルの呼び出しは require 等は不要で参照できます
全件取得する場合は .all を使います
内部的には単純に SELECT * が実行されています

今回は order を使っていますが他にも様々な SQL 構文を実行できるので興味があればこちらのリファレンスを御覧ください

View を作成する

実際に HTML として出力されるテンプレートを作成します
先程コントローラで Postgres から取得した @my_tasks の情報を HTML として出力してあげます
また一覧から登録もできるようにするため form も設置しています
create メソッドは後述するためまだ動作はしません

メソッド名と同じ erb ファイルは所定の場所に配置すると自動的にそれを呼び出してくれるようです

  • vim app/views/my_tasks/list.erb
<html>
  <head><title>List My Tasks</title></head>
<body>
<h2>Create My Task</h2>
<form method="POST" action="/my_tasks">
  <p>Title: <input type="text" name="title"></p>
  <p>Description: <br><textarea name="description" rows="4" cols="40"></textarea></p>
  <p>Public: <input type="checkbox" name="public" value="checked"></p>
  <p><button type="submit">Create</button></p>
</form>
<h2>List My Tasks</h2>
<ul>
<% @my_tasks.each do |my_task| %>
  <li>Title: <a href="/my_tasks/<%= my_task.id %>"><%= my_task.title %></a> Posted Date: <%= my_task.posted_date %></li>
<% end %>
</ul>
</body>
</html>

とりあえず動作確認する

この状態でアプリは動作するようになっています
まだデータは何もないので特に表示はされませんが HTML がエラーなく出力されることを確認できると思います

  • rails server

localhost:3000 にアクセスすれば確認できます

post, show を実装する

残りのメソッドをコントローラに実装します

  • vim app/controllers/my_tasks_controller.rb
class MyTasksController < ApplicationController
  protect_from_forgery

  def list
    @my_tasks = MyTask.order(posted_date: "DESC").all
  end

  def show
    id = params[:id]
    @my_task = MyTask.find(id)
  end

  def create
    my_task = MyTask.new
    my_task.title = params[:title]
    my_task.description = params[:description]
    my_task.public = params[:public]
    my_task.updated_date = Time.now
    if my_task.public
      my_task.posted_date = Time.now
    end
    my_task.save
    redirect_to '/my_tasks'
  end
end

protect_from_forgery は CSRF (ActionController::InvalidAuthenticityToken) 対策になります
特に説明することはないと思いますが showparams[:id] を取得して特定の id のタスクの詳細を表示します

create は form から送られてきた情報を params から取得し MyTask モデルのインスタンスにセットします
あとは .save をコールすれば Postgres にデータが格納されます
格納後は /my_tasks にリダイレクトします

show.erb

詳細を表示するページも作成します

<html>
  <head><title>Show My Task</title></head>
<body>
<h2>Show My Task</h2>
  <p>ID: <%= @my_task.id %></p>
  <p>Title: <%= @my_task.title %></p>
  <p>Description: <%= @my_task.description %></p>
  <p>Posted Date: <%= @my_task.posted_date %></p>
  <p>Updated Date: <%= @my_task.updated_date %></p>
  <p>Public: <%= @my_task.public %></p>
</body>
</html>

動作確認

これで今回実装するべきすべての機能を実装しました
アプリを起動して動作確認してみましょう

  • rails server

localhost:3000 にアクセスすると以下のようにデータの登録から確認までができるようになっていると思います

最後に

Rails6 + PostgreSQL で超簡単な Web アプリを作成してみました
超入門レベルの流れは掴めたかなと思います
Controller と Model の連携方法、Controller からどうやって View が呼び出されるか変数が渡せるか、辺りを理解できれば OK かなと思います

Rails は実装よりも新規アプリ作成時のエラー対応の方が大変なイメージがあります

2017年1月21日土曜日

Windows + Cygwin で Rails5 環境の構築

概要

Cygwin が動作している Windows 環境に Rails の環境を構築してみました

環境

  • Windows7 (6.1.7601)
  • apt-cyg 0.59
  • Ruby 2.2.4p230
  • gem 1.11.2
  • Rails 5.0.0

Ruby のインストール

  • apt-cyg install ruby
  • gem install bundler

必要なパッケージのインストール

  • apt-cyg install libxml2 libxml2-devel zlib zlib-devel libiconv libiconv-devel sqlite3 libsqlite3-devel

Rails のインストール

  • cd /path/to/work
  • bundle init
  • vim Gemfile
# A sample Gemfile
source "https://rubygems.org"

gem "rails"
  • bundle install

動作確認

  • rails new test
  • cd test
  • rails server --bind=0.0.0.0

でアプリが作成されることを確認しましょう
rails server 時に json がないと言われる場合は Gemfile に「gem “json”」を追記して再度 bundle install してください

停止する場合は Ctrl + c でいいのですが、自分の場合それでうまく停止できませんでした
その場合は ps -> kill -9 で停止しました

最後に

この手順とバージョンで Rails5 がインストールできました
nokogiri のインストールと rails new するときにパッケージが足りないというエラーが出ましたがその都度足りないパッケージを apt-cyg install しました

Vagrant やら docker やら使って Linux 環境上に作ろうかなと思ったんですが、 yak しそうだったんで Windows 上に直接構築してみました