20190405のRubyに関する記事は11件です。

プログラミングとはなに?小学生に説明してみようと思う

tl;dr

小学校からプログラミングを教えるそうで、2020年からは必修になるとのこと。

文部科学省 小学校プログラミング教育の手引

わたしの職業がWebエンジニア(プログラマー)ということもあって、小学生の子どもから「プログラミングとはなにか?」と聞かれるのですが、うまい説明が見つからない…

ということでまとめてみました。

プログラミングってなに?

プログラミングは英語で書くと「Programming」。
Program + ingということです。

現在進行形なので、無理やり訳すと「プログラムをしている状態」とか「プログラム作ること」となりますね。

プログラムってなに?

では「プログラム」ってなんなのか?

辞書で引いてみるとこんなふうに載っていました。

  1. 物事の予定。行事の進行についての計画。
  2. 映画・演劇・コンサートなどの演目や曲目,あらすじや解説などを書いた表や小冊子。
  3. コンピューターに,情報処理を行うための動作手順を指定するもの。また,それを作成すること。 (三省堂 大辞林より)

もともとは「予定」とか「計画」の意味だったのものが、コンピューター用語に転用されてコンピューターに「動作手順」を指定するものになったということです。

コンピューターの「動作手順」とは?

動作手順というのはなにかというと、コンピューターに対して

「どのように動くのか」

を命令(指示)するものです。

たとえば、コンピューターの画面にあいさつを表示させたいとすると

puts "Hello World!!"

と書きます。

こうすると画面には

Hello World!!

と表示されます(ここに書いたのはRubyという言語(言葉)で書いていますが、他にもいろいろな言語があります)。

こういった命令を組みあせて、コンピューターに様々な動きをさせるのがプログラミングです。

終わりに

これで小学生の子どもに理解できるだろうか…?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

世界初? "アイテムで繋がるSNS" を作ってみた。 ~顔やステータスよりも,持ち物で勝負する~

"アイテムで繋がるSNS"を初心者が2ヶ月で作って見ました

個人間で互いにレンタルができる匿名CtoCサービス(無料)があったら面白そうだと思い作りました。将来、企業への広告媒体としてのモデル(下ネタ以外でも)の可能性があるのかなと。作ってきた経緯等を此方の記事に書きたいと思います。ありそうでなかった?(無知でしたらすいません↓

個人的に"SSS(ソーシャル-シェアリング-システム)"と名付けました。
Ruby on Railsで作り、後ろでどんな技術が使ったのか簡単に解説します。
そして、少しエロいです(笑)

  • メッセージ機能
    メッセージ機能

  • ユーザー・検索機能

スクリーンショット 2019-04-03 21.20.37.png

  • Add-Item 機能

スクリーンショット 2019-04-03 21.28.44.png

  • 武器を買う
    商品購入ページ or ECサイトへ

...等をGoogle先生を調べながら実装しました。

URL
Den-app 

スペック

  • 使用言語等など

    • Ruby 2.5.1
    • Rails 5.2.1
    • Vagrant CentOS7
  • 著者プロフィール

    • プログラミング歴 約1年
      (去年の 5月13日〜14日位から)
    • Rubyのチェリー本 => Rails チュートリアル => クローンアプリ制作後,実際に自分の考えたアプリを作ろうと思い, "Den-app"を制作しました。
    • PG,転職探し中
    • 祝 初サイト:sunny:

- メッセージ機能(リアルタイム). ~ Action Cable ~

  • ここからは実際に使用した技術を2種類紹介いたします。
    どちらも, Rails5.2系で標準なので詳しい素晴らしい記事は他に多くあります。
  • "den-app"にどのような効果をもたらしたかを書きたいと思います。

1つ目は、個人間でのメッセージ機能をページ推移なしで行えるように,"ActionCable"を使用しました。これを使えば、非同期でサーバーと通信が出来ます。

messaging.gif

ActionCableは日本語のページ等も多く、技術自体は簡単に実装できたのですが、理想の挙動にするのが私の力不足で時間が掛かり、1ヶ月程掛かりました。
QItaは勿論の事、意外と英語用のプログラミング質問サイト等にサラッと金言が載ってたりして血眼で探した記憶があります。

こういう非同期で通信することを"Ajax化"と呼ぶそうで、ActionCableではないですがden-appではもう一箇所この技術を利用している箇所があります。

  • 借りたい申請

items.gif

皆さんの持ち物に"借りたい”という申請をするページですが、"Ajax化"で、更新せずとも Create & Destroyアクションを送信できます。素晴らしいですね:eyes:

- 画像アップロード. ~ Active storoge ~

Railsで画像アップロードと聞くと、現在でもやはり,CarrierWaveが人気のようです。探して見ても圧倒的に情報が載って居ました。
Den-appではrails標準の最近追加された,"Active storoge" とストレージにAWSのS3を用いています。採用理由としては、

  • 新しい技術好き
  • gem追加が要らない
  • 最近の紹介ページが意外と多い(2018年頃~)
  • 名前が格好良い(笑)

からです。Modelに

user.rb
class User < ApplicationRecord
  has_one_attached :image
  ....
end

とてもシンプルで使いやすいです。
S3との相性?も良いらしく、バケットを追加後アップデート出来るようになりました。CarrierWaveを使っている人も一度使って見ては如何でしょうか?
画像リサイズやデフォルトイメージ(初期画像)も調べればできます。

  • プロフィール編集機能

edit.gif

- Add-Item 機能で"Active Stroge"は使用されています。クローンアプリを練習した時、CarrierWaveを使用したのですが、現代の段階では標準装備がActive Storageよりも多く便利です。私としても使い分けて使用していきたいと思います。

本番へのデプロイ

Railsから繋げる本番環境としてはHerokuを使用しています。Pushコマンド1つで更新できるのはとても魅力がありますね。

ただ、現在直面している問題がネイキッドドメイン(www無し)からDen-app(www有り)へのリダイレクトが出来ない。。。Railsの機能として,

config/production.rb
Rails.application.configure do
  ...
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true
  ....
end

でSSHへのリダイレクトや,○○○..herokuapp.comから独自ドメインへのリダイレクトは設定できたが、ネイキッドドメインからのアクセスが未だに出来ない↓
今後とも勉強していきます。優しい人、教えて下さい。

総括

纏めて見ましたが、一言で言うと,"Ruby on Railsは凄まじい"に尽きます。
私のような初心者でも、完璧ではなくともアイデアをサービスとして開発できました。

今後とも、Den-appの研磨は勿論のこと、色々作りたいサービスがあるので影でひっそりと一人で作っていきたいと思います。都内で週末などに、一緒にプログラミングをできる方がいればな〜と思っています。(気軽にコメントやTwitterのDM等で連絡くれると嬉しいです。)

Den-appに少しでも興味を持った方は是非登録して見て下さいね。
詳しい使い方は"HowToUse"に載っています。素敵な事があるかもしれません。

記事を読んで頂き、大変感謝いたします。
Den-app 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

世界初? 一人でひっそりと "アイテムで繋がるSNS" を作ってみた。 ~顔やステータスよりも,持ち物で勝負する~

"アイテムで繋がるSNS"を初心者が2ヶ月で作って見ました

個人間で互いにレンタルができる匿名CtoCサービス(無料)があったら面白そうだと思い作りました。将来、企業への広告媒体としてのモデル(下ネタ以外でも)の可能性があるのかなと。作ってきた経緯等を此方の記事に書きたいと思います。ありそうでなかった?(無知でしたらすいません↓

個人的に"SSS(ソーシャル-シェアリング-システム)"と名付けました。
Ruby on Railsで作り、後ろでどんな技術が使ったのか簡単に解説します。
そして、少しエロいです(笑)

  • メッセージ機能
    メッセージ機能

  • ユーザー・検索機能

スクリーンショット 2019-04-03 21.20.37.png

  • Add-Item 機能

スクリーンショット 2019-04-03 21.28.44.png

  • 武器を買う
    商品購入ページ or ECサイトへ

...等をGoogle先生を調べながら実装しました。

URL
Den-app 

スペック

  • 使用言語等など

    • Ruby 2.5.1
    • Rails 5.2.1
    • Vagrant CentOS7
  • 著者プロフィール

    • プログラミング歴 約1年
      (去年の 5月13日〜14日位から)
    • Rubyのチェリー本 => Rails チュートリアル => クローンアプリ制作後,実際に自分の考えたアプリを作ろうと思い, "Den-app"を制作しました。
    • PG,転職探し中
    • 祝 初サイト:sunny:

- メッセージ機能(リアルタイム). ~ Action Cable ~

  • ここからは実際に使用した技術を2種類紹介いたします。
    どちらも, Rails5.2系で標準なので詳しい素晴らしい記事は他に多くあります。
  • "den-app"にどのような効果をもたらしたかを書きたいと思います。

1つ目は、個人間でのメッセージ機能をページ推移なしで行えるように,"ActionCable"を使用しました。これを使えば、非同期でサーバーと通信が出来ます。

messaging.gif

ActionCableは日本語のページ等も多く、技術自体は簡単に実装できたのですが、理想の挙動にするのが私の力不足で時間が掛かり、1ヶ月程掛かりました。
QItaは勿論の事、意外と英語用のプログラミング質問サイト等にサラッと金言が載ってたりして血眼で探した記憶があります。

こういう非同期で通信することを"Ajax化"と呼ぶそうで、ActionCableではないですがden-appではもう一箇所この技術を利用している箇所があります。

  • 借りたい申請

items.gif

皆さんの持ち物に"借りたい”という申請をするページですが、"Ajax化"で、更新せずとも Create & Destroyアクションを送信できます。素晴らしいですね:eyes:

- 画像アップロード. ~ Active storoge ~

Railsで画像アップロードと聞くと、現在でもやはり,CarrierWaveが人気のようです。探して見ても圧倒的に情報が載って居ました。
Den-appではrails標準の最近追加された,"Active storoge" とストレージにAWSのS3を用いています。採用理由としては、

  • 新しい技術好き
  • gem追加が要らない
  • 最近の紹介ページが意外と多い(2018年頃~)
  • 名前が格好良い(笑)

からです。Modelに

user.rb
class User < ApplicationRecord
  has_one_attached :image
  ....
end

とてもシンプルで使いやすいです。
S3との相性?も良いらしく、バケットを追加後アップデート出来るようになりました。CarrierWaveを使っている人も一度使って見ては如何でしょうか?
画像リサイズやデフォルトイメージ(初期画像)も調べればできます。

  • プロフィール編集機能

edit.gif

- Add-Item 機能で"Active Stroge"は使用されています。クローンアプリを練習した時、CarrierWaveを使用したのですが、現代の段階では標準装備がActive Storageよりも多く便利です。私としても使い分けて使用していきたいと思います。

本番へのデプロイ

Railsから繋げる本番環境としてはHerokuを使用しています。Pushコマンド1つで更新できるのはとても魅力がありますね。

ただ、現在直面している問題がネイキッドドメイン(www無し)からDen-app(www有り)へのリダイレクトが出来ない。。。Railsの機能として,

config/production.rb
Rails.application.configure do
  ...
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true
  ....
end

でSSHへのリダイレクトや,○○○..herokuapp.comから独自ドメインへのリダイレクトは設定できたが、ネイキッドドメインからのアクセスが未だに出来ない↓
今後とも勉強していきます。優しい人、教えて下さい。

総括

纏めて見ましたが、一言で言うと,"Ruby on Railsは凄まじい"に尽きます。
私のような初心者でも、完璧ではなくともアイデアをサービスとして開発できました。

今後とも、Den-appの研磨は勿論のこと、色々作りたいサービスがあるので影でひっそりと一人で作っていきたいと思います。都内で週末などに、一緒にプログラミングをできる方がいればな〜と思っています。(気軽にコメントやTwitterのDM等で連絡くれると嬉しいです。)

Den-appに少しでも興味を持った方は是非登録して見て下さいね。
詳しい使い方は"HowToUse"に載っています。素敵な事があるかもしれません。

記事を読んで頂き、大変感謝いたします。
Den-app 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sidekiqのソース読んでみたメモ

sidekiqのソースを読んでみた

  • sidekiqを使ってるけどなかってどうなってるの?というのを調べることがあったのでまとめ

前提

  • バージョン
    • v5.2.5
  • 読んでみたタイミング
    • 2019/03
  • ここで書かないこと
    • siekiqの使い方
    • Thread周り
  • 基本的なとこだけ読んでます
  • 読んでみたなので正確ではないこともあります。。

クライアント側(jobを積むほう)

Sidekiq::Worker

  • 実行する処理とかを用意するクラスにincludeするモジュール
  • includeしたクラスにperform_asyncとかが特異メソッドとして追加される

#perform_async

  • 中でSidekiq::Clientのオブジェクトを生成してpushを読んでる

Sidekiq::Client

  • redisにjobをつむところの役割をやってる

#push

  • workerに渡す引数+jobのidを用意して整形してatomic_pushに投げる

#atomic_push

  • redisのqueuesていうキーにつむjobのキュー名を入れている(タイプがsetなので同じものを入れるときは重複しない)
  • redisのqueue:キュー名のキーにjobを追加する

サーバー側

  • jobを実行するほう

登場するクラス

  • Sidekiq::CLI
    • サーバー側の起動を行う
    • そのあとは停止などのコマンドを受け付ける
  • Sidekiq::Launcher
    • このオブジェクトのrunを呼べばサーバー側が動く
    • jobを取ってきて処理するのと、retryやスケジュールからqueueを積む処理を起動してくれるのでランチャー
  • Sidekiq::Manager
    • Sidekiq::Processorを作成&ぐるぐる実行させる
  • Sidekiq::Processor
    • 一定時間ごとにqueueをチェックしに行って処理を実行
    • こいつがperformを呼び出してる
  • Scheduled::Poller
    • 即時実行じゃないjobや一度失敗したjpbを確認して、実行タイミングになったらqueueに積む

sidekiq起動のところ

bin/sidekiq

  • Sidekiq::CLI.instance
    • Sidekiq::CLIのインスタンスを生成(instanceってなんだ?と思ったけどシングルトンだった。。)
  • Sidekiq::CLI#parse
    • 設定とかをsetしてる
  • Sidekiq::CLI#run
    • Sidekiq::Launcherのインスタンスを生成&Sidekiq::Launcher#runを呼び出したあとはコマンドを受け付けるためぐるぐるし続ける。
    • Sidekiq::Launcher#run
    • Sidekiq::Manager#startとSidekiq::Scheduled::Poller#startを呼び出す
      • Sidekiq::Manager#start
      • Sidekiq::Processor#startを呼び出す
        • Sidekiq::Processor#start
        • スレッドでrunを実行
          • Sidekiq::Processor#run
          • jobがあればとってきて該当workerのperformを実行をぐるぐる。
      • Sidekiq::Scheduled::Poller#start
      • redisのretryとscheduleの中で実行タイミングになったものを取り出してqueueにつむ。
        • retryとscheduleはzsetで、timestampをキーにしてて、今の時間よりも小さいものーでとってるっぽい

ざっくりかくと
Sidekiq::CLI -> Sidekiq::Launcherを叩いてあとは入力待ちぐるぐる
Sidekiq::Launcher#run -> Sidekiq::Manager#startとSidekiq::Scheduled::Poller#startを叩く
Sidekiq::Manager#start -> Sidekiq::Processorをぐるぐるさせる
Sidekiq::Scheduled::Poller -> ぐるぐる

おまけでRedisに登録されるもの

※ namespaceは省く

キー type
schedule zset 予定されたジョブが入っている
queues set 一度でもジョブが積まれたキューのリスト
stat:processed string 完了したジョブの数
stat:processed:yyyy-mm-dd string 該当日に完了したジョブの数
stat:failed string 失敗したジョブの数
stat:failed:yyyy-mm-dd string 該当日に失敗したジョブの数
queue:キュー名 list そのキューの待機状態のジョブのリスト
limit:processes set TODO (limit:heartbeatのUUIDが入っていた。。)
limit: heartbeat:UUID string TODO (trueとか入ってる。。)
host:port:xxxx:workers hash そのworkerが現在実行中のジョブの情報

感想

  • ランチャーってなんぞと思ったけど、一括で必要なのを起動してくれるもののことらしいと今回知った。。
  • Threadとか詳しくないのでいつかちゃんと触って理解しよう。。
  • redisの型とか全然知らなかったのでちょっと知れてよかった。
  • gemを読んでみたって記事はありなのか。。?(お勉強メモなのでお許しください。。)
  • 顔文字とか絵文字のメソッドがちょいちょいあって面白かった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails/JavaScript】Googleマップ上にある同じ座標の複数マーカーをずらす

個人開発のWebアプリまちかどルートv5.62への実装メモです。

Googleマップのマーカーが重なる

まちかどルートでは位置情報付きの投稿をすべて「ルートマップ」と名付けたGoogleマップ上にマーカー表示させています。

ちなみにgmaps4railsgeocoderというgemを使っています。

使い方についてはこちらの記事などを参考にしました。

しかしながらまったく同じ座標の投稿があると複数のマーカーが重なってしまい、とても不便です。

maxRandomDistanceを使う

handler = Gmaps.build('Google', { markers: { maxRandomDistance: 5 } });

viewに使うhandler = Gmaps.build('Google');の部分を上記のようなコードに変更すると、同じ座標にある複数のマーカーを下の写真のようにずらして表示できるようになります。

ランダムにずれるので細かな調整には不向きですが、簡単な対応策として良いのではないでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで「CarrierWave+fog」を使ってAWSのS3へアップロード

はじめに

Railsアプリを外部サービスと連携させてみようと思い、AWSが提供するS3へ画像や動画をアップロードしてみた過程をまとめていきます。
今回はCarrierWaveで単純にアップロード機能を利用し、fogでクラウド上のs3へ簡易的にアップロードできるようにしています。

AWS上での設定

S3の詳しい利用法の要点をまとめます。

①AWSアカウントの取得
 AWSのアカウントを取得。原則としてクレジットカードの登録が必要になります。アカウント作成から12か月間は無料枠(最大5GB)としてS3が利用できます。

②IAMユーザーの作成
 続いてIAMユーザーを作成します。IAMとはAWS上のサービスを操作するユーザーと、ユーザーアクセス権限を管理するものです。
IAMがなかった場合、フルアクセスのAWSアカウントをみんなで共有することになります。
ユーザーがアクセスするための認証情報(アクセスキー、ポリシーの設定)を取得しS3へアクセスします。

※ここで取得したアクセスキーIDとシークレットアクセスキーは絶対に控える&定期的に更新する

③バケットの作成
 バケットとは、ファイルなどのオブジェクトを格納するための入れ物です。ここでバケット名を決めるのですが、注意が必要です。AWSバケット名の命名規則に従った名前を付けてください。https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/BucketRestrictions.html
自分の場合、バケット名に不適切な文字を記述してしまい、原因がわからずS3にいつまでたってもアクセスできない事態に見舞われました。

続いてバケットのリージョンを設定します。リージョンとはS3のサービスが提供されている場所を意味します。「アジア・パシフィック(東京)」の場合、使用する資格情報は「ap-northeast-1」となります。

CarrierWaveの導入

gem 'carrierwave'
gem 'fog'

アップロードファイルの作成
$ rails g uploader file
app/uploaders/file_uploader.rbが生成されます。

file_uploader.rb
class FileUploader < CarrierWave::Base
  require 'streamio-ffmpeg'

  if Rails.env.production?
    storage :fog
  else
    storage :file 
  end

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  def extension_white_list
    %w(jpg jpeg gif mp4 MOV wmv)
  end

  version :screenshot do
    process :screenshot
    def full_filename (for_file = model.logo.file)
      "screenshot.jpg"
    end
  end

  def screenshot
    tmpfile = File.join(File.dirname(current_path), "tmpfile")

    File.rename(current_path, tmpfile)

    movie = FFMPEG::Movie.new(tmpfile)
    movie.screenshot(current_path + ".jpg", { resolution: '500x600' },preserve_aspect_ratio: :width)
    File.rename(current_path + ".jpg", current_path)

    File.delete(tmpfile)
  end
end

サムネイル用にffmpegを使用しています。
今回はアップロードされたファイルが画像でも動画でも、一旦tmpフォルダーにjpgファイルとして格納し、最終保存先を開発環境時はローカルに、本番環境時はfogに設定しています。

続いてcarrierwave.rbを作成します。

carrierwave.rb
require 'carrierwave/storeage/fog'

CarrierWave.configure do |config|

  config.fog_provider = 'fog/aws'

  config.fog_credentials = {
    provider: 'AWS',
    aws_access_key_id: 'アクセスキー',
    aws_secret_access_key: 'シークレットアクセスキー',
    region: 'リージョン',
  }

  config.fog_directory = 'バケット名'
  #日本語ファイル名の設定
  CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
  config.cache_dir = "#{Rails.root}/tmp/uploads"
end

モデルにuploaderを紐づけます。

gallery.rb
class Gallery < ApplicationRecord
  mount_uploader :file, FileUploader

viewの実装は今回は割愛します。
これで本番環境時にS3にアップロードすることができました。

おわり

特に基盤となってくるuploaderとcarrierwaveの設定はまだ完全に理解していないし、不十分なところもあると思うので今後さらに深堀していきたいです。

参考

https://qiita.com/junara/items/1899f23c091bcee3b058
http://vdeep.net/rubyonrails-carrierwave-s3
https://qiita.com/yukiyukimiyaiwa/items/a9e0103c8342b81f6ac1
https://stackoverflow.com/questions/15717368/uploading-image-to-s3-using-carrierwave-and-fogs-bad-uri

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mac(10.4 Mojave)で gem install scrypt に失敗(ld: symbol(s) not found for architecture i386)

Macを10.4(Mojave)にアップデートし、Xcode Command Line Tools(macOS 10.14) for Xcode 10.2をインストールしたあと、gem install scryptの実行に失敗しました。

$ gem install scrypt -v 2.0.2
Building native extensions.  This could take a while...
ERROR:  Error installing scrypt:
    ERROR: Failed to build gem native extension.
...
gcc -bundle -o x86_64-darwin/libscrypt_ext.bundle x86_64-darwin/crypto_scrypt-sse.o x86_64-darwin/memlimit.o x86_64-darwin/scrypt_calibrate.o x86_64-darwin/scrypt_ext.o x86_64-darwin/scryptenc_cpuperf.o x86_64-darwin/sha256.o -fexceptions -arch x86_64 -arch i386
ld: warning: The i386 architecture is deprecated for macOS (remove from the Xcode build setting: ARCHS)
ld: warning: ignoring file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libSystem.tbd, missing required architecture i386 in file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libSystem.tbd
Undefined symbols for architecture i386:
...

文字通り10.4(Mojave)向けのCommand Line Toolsから-arch i386をサポートしなくなったことが原因らしい。
https://developer.apple.com/download/more/ から Xcode Command Line Tools(macOS 10.13) for Xcode 10.1 をインストールしたら、gem install scryptできるようにしました。
(ただ根本的な解決にはなっていないので、要調査)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UTF-8のコードポイントはどうやって高速に数えるか

UTF-8文字列からコードポイント数を計算するアルゴリズムについて紹介します。コードポイント数カウントは、シンプルに書くのはそれほど難しくないものの、高効率な実装は意外にややこしいです。

内容は二本立てです。

  • 実践的な実装について、Ruby(CRuby)の内部実装(string.c)で使われているものを紹介します。
  • 標準Cの範囲を超えて、SIMD命令(AVX/AVX2)を使った実装についても述べます
    • 軽く検索する限りだと既知のアルゴリズムが見当たらなかったので、アドホックな実装をひねり出しましたが、そんなに効率は悪くなさそうです

おまけで簡単な性能評価をやってみました。
なお、UTF-8文字列はバリデーション済み(不正なシーケンスでないことが分かっている)であるとします。

Rubyの内部実装だとどうやっているか

まずは、それがコードポイントの先頭バイト(leading byte)かを判定するis_utf8_lead_byteを定義します。正体はプリプロセッサマクロです。

#define is_utf8_lead_byte(c) (((c)&0xC0) != 0x80)

https://github.com/ruby/ruby/blob/v2_6_2/string.c#L1629

0xC0つまり0b11000000とマスクすると、上2ビットだけが残ります。UTF-8の先頭でないバイトは0b10xxxxxxという並びになっているので、もし非先頭バイトであればマスク後には0b10000000となりますが、これはつまり比較演算!= 0x80で先頭バイトか判定できることを意味します。

このマクロを使うと、簡単にコードポイントカウントを実装できます。

#define is_utf8_lead_byte(c) (((c)&0xC0) != 0x80)
int64_t count_utf8_codepoint(const char *p,  const char *e) {
    while (p < e) {
        if (is_utf8_lead_byte(*p)) len++;
        p++;
    }
    return len;
}

さて、ここまでは単純ですが、Ruby処理系における実装はもう少し工夫がしてあります。
上記の素朴な実装ではバイト単位で文字列処理をしていますが、現代のPC・サーバなどの典型的な環境であれば32/64ビット整数を不自由なく扱えるため、相対的に非効率です。
そこで、複数バイトをuintptr_t型でまとめて読み込み、含まれる全ての先頭バイトの数を一気に数えるということをします。コードポイント数は先頭バイト数に等しい ので、コードポイントを数えるには先頭バイトを数えればよいのです。

#define NONASCII_MASK UINT64_C(0x8080808080808080)
static inline uintptr_t
count_utf8_lead_bytes_with_word(const uintptr_t *s)
{
    uintptr_t d = *s;
    d = (d>>6) | (~d>>7);
    d &= NONASCII_MASK >> 7;
    return __builtin_popcountll(d);
}

https://github.com/ruby/ruby/blob/v2_6_2/string.c#L1631-L1665

※ (インクルードヘッダを除き)コード片のみでコンパイルできるように少し書き換えています。また、sizeof(uintptr_t) == 8の環境を前提に記述しています。

まとめて計算してるのでちょっとややこしいですが、次の点に注意してください。

  • NONASCII_MASK >> 7は各バイトの最下位ビットだけが立っているマスク
    • dはこれとbit andされるので、最下位ビットのみが残る
  • (d>>6) | (~d>>7)の各バイト最下位ビットに着目すると「7ビット目が立っている or 8ビット目が落ちている」判定になっている
    • 7ビット目が立っている:UTF-8の先頭でないバイトは、常に0b10xxxxxxという並びになっていることを思い出すと、つまり先頭バイト
    • 8ビット目が落ちている:ASCIIなので先頭バイト

ビット演算後のdは、各バイトについて最下位ビットが立っていると先頭バイトだということを表すようになります。その他のビットはマスクされているので常に0です。したがって、含まれる先頭バイトの数を数えるには、popcntをすればいいというわけです。

最後にようやくですが、count_utf8_lead_bytes_with_wordを使った実装の全体像を示します。

int64_t ruby_count_utf8_codepoint(const char *p, const char *e)
{
    uintptr_t len = 0;
    if ((int)sizeof(uintptr_t) * 2 < e - p) {
        const uintptr_t *s, *t;
        const uintptr_t lowbits = sizeof(uintptr_t) - 1;
        s = (const uintptr_t*)(~lowbits & ((uintptr_t)p + lowbits));
        t = (const uintptr_t*)(~lowbits & (uintptr_t)e);
        while (p < (const char *)s) {
            if (is_utf8_lead_byte(*p)) len++;
            p++;
        }
        while (s < t) {
            len += count_utf8_lead_bytes_with_word(s);
            s++;
        }
        p = (const char *)s;
    }
    while (p < e) {
        if (is_utf8_lead_byte(*p)) len++;
        p++;
    }
    return (long)len;
}

https://github.com/ruby/ruby/blob/v2_6_2/string.c#L1680-L1700

なんでこんなに複雑な実装なんだという話ですが、count_utf8_lead_bytes_with_wordを正しく動作させるためには、uintptr_tのnバイト境界(典型的にはn = 4 or 8だと思います)にアライメントされたポインタから読み出す必要があるからです。具体的なやり方としては次のようになります。

  • lowbitsを足してからマスクすることで、pをアライメントされた位置まで進めてsとする
  • lowbitsを引いてからマスクすることで、eをアライメントされた位置まで戻してtとする

こうして前処理されたs, tに対してならcount_utf8_lead_bytes_with_wordが使えます。
もちろんs,tp, eにはズレが生じるかもしれないで、普通のループ処理を使って前後の差分を埋めています。

AVXによるアルゴリズム/実装

アルゴリズム、というほどのものではないので、実装と並行して解説していきます。

基本的な方針としては、UTF-8バリデーションのアルゴリズムと同じく、バイトの上位ニブルを見れば先頭バイトかどうかわかるという性質を使います。vpshufbを使えば先頭バイトのあった位置だけに1を立てることができます。
1の数え方ですが、SIMDでは水平方向に加算するのはコストがかかるので、32バイト単位で切り出されたベクトルをそのまま足し合わせていくことにします。ただし、バイトで表せる値の範囲は0〜255であることから、ベクトル加算は255回を超えて行うとオーバーフローが起こりかねません。
回避策としてはループを分割し、255回ごとに水平加算で値を集約していくことにします(もちろん入力が無くなったら255回以前にループは終了します)。こうすると水平加算は最小で1回、最大でも255回に1回しか実行されないので、相対的にコストが低くなると期待できます。

さて実装です。まず、補助関数として、バイト単位で水平加算する関数を定義しておきます。

inline int32_t avx2_horizontal_sum_epi8(__m256i x)
{
    __m256i sumhi = _mm256_unpackhi_epi8(x, _mm256_setzero_si256());
    __m256i sumlo = _mm256_unpacklo_epi8(x, _mm256_setzero_si256());
    __m256i sum16x16 = _mm256_add_epi16(sumhi, sumlo);
    __m256i sum16x8 = _mm256_add_epi16(sum16x16, _mm256_permute2x128_si256(sum16x16, sum16x16, 1));
    __m256i sum16x4 = _mm256_add_epi16(sum16x8, _mm256_shuffle_epi32(sum16x8, _MM_SHUFFLE(0, 0, 2, 3)));
    uint64_t tmp = _mm256_extract_epi64(sum16x4, 0);
    int32_t result = 0;
    result += (tmp >> 0 ) & 0xffff;
    result += (tmp >> 16) & 0xffff;
    result += (tmp >> 32) & 0xffff;
    result += (tmp >> 48) & 0xffff;
    return result;
}

avx2_horizontal_sum_epi8を用いると、32の倍数についてコードポイントをカウントする関数は比較的簡単に書けます。

int64_t avx_count_utf8_codepoint(const char *p,  const char *e)
{
    // `p` must be 32B-aligned pointer
    p = static_cast<const char *>(__builtin_assume_aligned(p, 32));
    const size_t size = e - p;
    int64_t result = 0;
    for (size_t i = 0; i + 31 < size;) {
        __m256i sum = _mm256_setzero_si256();
        size_t j = 0;
        for (; j < 255 * 32 && (i + 31) + j < size; j += 32) {
            const __m256i table = _mm256_setr_epi8(
                    1, 1, 1, 1, 1, 1, 1, 1, //     .. 0x7
                    0, 0, 0, 0,             // 0x8 .. 0xB
                    1, 1, 1, 1,             // 0xC .. 0xF
                    1, 1, 1, 1, 1, 1, 1, 1, //     .. 0x7
                    0, 0, 0, 0,             // 0x8 .. 0xB
                    1, 1, 1, 1              // 0xC .. 0xF
                    );
            __m256i s = _mm256_load_si256(reinterpret_cast<const __m256i *>(p + i + j));
            s = _mm256_and_si256(_mm256_srli_epi16(s, 4), _mm256_set1_epi8(0x0F));
            s = _mm256_shuffle_epi8(table, s);
            sum = _mm256_add_epi8(sum, s);
        }
        i += j;
        result += avx2_horizontal_sum_epi8(sum);
    }
    return result;
}

内側のループでベクトルごとの加算をし、外側のループで加算後のベクトルの要素値を集約してresultに足していきます。
tableはUTF-8の上位ニブルから先頭バイトを検出し、先頭バイトのみを1にするための変換テーブルです。

内側のループの条件式j < 255 * 32 && (i + 31) + j < sizeは少しややこしいです。ループ変数jは32ずつ加算されるので、条件式の前半j < 255 * 32はループを255回で打ち切るという意味です。後半の(i + 31) + j < sizeは、入力長さsizeを32の倍数を超えてはみ出さないためのガードです。内側のループが終了するとき、ijが加算されるので外側ループのi + 31 < sizeも0になり、ループは終了します。

コラボ実装

32の倍数からはみ出た部分についてはRuby実装と組み合わせることが可能です。
今回は、はみ出た部分は1バイトずつ処理する実装で埋めてみました。pが32B境界に整列していない可能性もあるため、ベクトル処理の前に32B境界まで1バイトずつスカラで進める処理も手前に挿入しました。

int64_t count_utf8_codepoint(const char *p, const char *e)
{
    int64_t count = 0;
#if defined(__AVX2__)
    // increment `p` to 32B boundary
    while (((uintptr_t)p % 32) != 0) {
        if (is_utf8_lead_byte(*p)) count++;
        p++;
    }
    // vectorized count
    count += avx_count_utf8_codepoint(p, e);
    p += static_cast<uintptr_t>(e - p) / 32 * 32;
#endif
    while (p < e) {
        if (is_utf8_lead_byte(*p)) count++;
        p++;
    }
    return count;
}

性能評価

今回は、長い文字列に対して十分にスループットが出ているか、それは定量的にどの程度かを見ていきます。

性能評価の条件について簡単に述べます。

  • 環境: Ubuntu 18.04.1 on VirtualBox on MBP Late 2013
    • つまりCPUはHaswell世代
  • 文字列のサイズは100MiB
    • ちゃんとした文字列ではなく乱数列を使う
  • 測定値としてrdtsc命令から取得したクロックサイクル数を使う
    • 測定は3回行い、中央の値をとる
  • 100MiBだと1回の処理が短すぎるので、同じ処理を100回した区間を測定する
  • コンパイラとしてClang 8.0とGCC 7.3.0を使う
    • コンパイラオプション(共通): -O3 -masm=intel --std=c++14 -mavx -mavx2 -Wall -Wextra

測定対象は、紹介したスカラ版と、Ruby版と、コラボ実装の節で述べたcount_utf8_codepoint(以下AVX版)とします。

測定結果

実装 コンパイラ クロックサイクル数(Mclk)
スカラ版 GCC 7323
スカラ版 Clang 8793
Ruby版 GCC 4573
Ruby版 Clang 2643
AVX版 GCC 2361
AVX版 Clang 2345

なぜこのような結果になったのか?

全体的な結果としてはスカラ版が最も遅く、AVX版が最も高速になりました。

Ruby実装はGCCとClangで大幅な差が付いていますが、これはClangの自動ベクトル化がうまく効いているからのようです。
GCCの自動ベクトル化は、Ruby版の最も重い場所(while (s < t)のループ)について効いておらず、そこで大幅な差が付いています。

興味深いことに、スカラ版を自動ベクトル化した場合には、GCCの方が効率的なコード生成ができているように見受けられます。Ruby版のアルゴリズムからClangはヒントを得たんでしょうか……測定ミスでなければいいのですが。

同じ処理系(Clang)についてRuby版とAVX版を比較すると、AVXコードを手書きすることで1割ほど高速化できています。
しかしこれはコンパイラの支援を受けることで、比較的たやすくintrinsics手書きの9割程度の性能を得られたとも言えるため、あとはコストパフォーマンスの問題になりそうだと見受けられます。

ちなみに実時間としては、AVX版-Clangはおよそ0.838秒でした。つまり12GiB/s程度は出ていることになります。文字列が十分大きいためキャッシュは効いておらず、このMBPに付いているDDR3-1600 2chメモリは理論帯域25.6GiB/sであることを踏まえると、およそ帯域の5割ほどを使えていることになります。シングルスレッドのプログラムとしては十分すぎるような気はします。

まとめ

  • UTF-8のコードポイントカウントを題材にして、実践的な実装を紹介しました
  • UTF-8バリデーションアルゴリズムを応用したカウントアルゴリズムを示し、文字列が巨大である場合について実用性を検証しました
    • コンパイラによって性能差・特性差が大きいことも発見しました

Appendix

ソースコード

https://gist.github.com/saka1/5bdd0f0ad4e498d22258982f6b9699c5

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RuboCopの設定に書く相対パスはどこからの相対パスか?

RuboCopのv0.67で確認している話です。以前・以降のバージョンではもしかしたら違うかもしれないので注意してください。

document: https://docs.rubocop.org/en/latest/configuration/

TL;DR

inherit_fromに書く相対パス

inherit_fromが書かれた設定ファイルからの相対パス

Include/Excludeに書く相対パス

  • それが書かれた設定ファイルが.rubocopで始まるファイル名の場合
    • その設定ファイルからの相対パス
  • 設定ファイル名が.rubocopで始まらない場合
    • rubocopコマンドを実行したディレクトリからの相対パス

答えとしては上記で終わりですが、これがどういうケースで問題になりやすいのか(ハマりはやすいのか)、というのも書いているので良かったら最後まで見てください :smile:

RuboCopでパスが書ける部分

inherit_from: .rubocop_todo.yml

Style/CollectionMethods:
  Exclude:
    - 'foo/bar.rb'
  Include:
    - 'hoge/huga.rb'
  • inherit_from
    • 共通の設定ファイルなどを継承できる
  • Include/Exclude
    • Copを実行する対象・実行しない対象を宣言できる

RuboCopでは上記の2つにパスを書くことができます。
どちらも絶対パス・相対パスどちらでもかけますが、通常は相対パスになると思います。

ここで書く、相対パスはどこから見た相対パスなのか?という記事です。

inherit_fromに書く相対パス

こちらはとてもシンプルです。

inherit_fromに書く相対パスは、inherit_fromが書いてある設定ファイルからの相対パスになります。

.rubocop.yml
inherit_from: '../.rubocop_todo.yml'

例えば上記の場合、.rubocop.ymlの中で.rubocop_todo.ymlを読み込んでいますが、
これは、.rubocop.ymlからみて../にある.rubocop_todo.ymlを読み込みます。

treeでいうと以下のような関係になります。(baseというディレクトリ名は適当です。)

.
├── .rubocop_todo.yml
└── base
     └── .rubocop.yml

Include/Excludeに書く相対パス

こちらがややこしいです。

Include/Excludeの設定が書かれている設定ファイルの名前によって変わります!

  • ファイル名が.rubocopで始まる設定ファイルの場合
    • その設定ファイルからの相対パス
    • 例: .rubocop.yml.rubocop_todo.ymlなど
  • それ以外の設定ファイルの場合
    • rubocopコマンドを実行したディレクトリからの相対パス
    • 例: .my_rubocop_config.ymlrubocop.yml(ドットなし)など

具体例

.
├── config
│   ├── .my_rubocop_config.yml
│   ├── .rubocop_todo.yml
│   └── .rubocop.yml
└── src
     └── sample.rb

上記のようなディレクトリ構成があるとします。configディレクトリ以下に、3つのrubocopの設定ファイルがあります。

.rubocop.yml
inherit_from:
    - .my_rubocop_config.yml
    - .rubocop_todo.yml

.rubocop.ymlの中でほか2つの設定ファイルを読み込みます。

$ rubocop -c config/.rubocop.yml
rubocopコマンドは、ルートディレクトリから実行するとします。

上記のような前提とします。

この状態で、3つの設定ファイルそれぞれで、src/sample.rbをExcludeする設定を書きたいとします。

以下のようになります。

config/.rubocop.yml
# config/.rubocop_todo.yml
Style/CollectionMethods:
  Exclude:
    - '../src/sample.rb' # .rubocopで始まるので、設定ファイルからの相対
config/.rubocop_todo.yml
Style/CollectionMethods:
  Exclude:
    - '../src/sample.rb' # .rubocopで始まるので、設定ファイルからの相対
config/.my_rubocop_config.yml
Style/CollectionMethods:
  Exclude:
    - 'src/sample.rb' # .rubocopではないので、実行ディレクトリからの相対
    # or 
    # - '**/sample.rb'
  • ファイル名が.rubocopで始まる設定ファイルの場合
    • その設定ファイルからの相対パス
  • それ以外の設定ファイルの場合
    • rubocopコマンドを実行したディレクトリからの相対パス

なので、
config/.rubocop.ymlconfig/.rubocop_todo.ymlでは、../src/sample.rbというように../でたどる必要があります。
config/.my_rubocop_config.ymlは、rubocopコマンドを実行したディレクトリからの相対パスなので、'src/sample.rb'**/sample.rbとかけます。

なので、RuboCopの設定ファイルをtoolsやconfigディレクトリなどに置いて、rubocopコマンドはリポイ取りルートなどから実行するようなケースでは、
.rubocopから始まらない設定ファイルにするのをおすすめします。

どういう時に問題になりやすいか

RuboCopがデフォルトで読み込むのは.rubocop.ymlなので、この名前で設定ファイルを書かれている方が多いかと思うのですが、
.rubocop.ymlがリポジトリルート(またはrubocopコマンドを実行するディレクトリ)においてあるのであれば、問題になりません。

ですが、設定ファイルをconfigディレクトリやtools/rubocop等においてる場合に問題になりやすいです。

.
├── src
│   ├── admin_server
│   │   └── test
│   │       └── bar.rb
│   └── api_server
│       └── test
│           └── foo.rb
└── tool
    └── rubocop
        └── config
            └── rubocopの設定ファイル

上記のような構成にし、rubocopコマンドをリポジトリルートなどで実行する場合は、
設定ファイルは.rubocopで始めないほうが楽です。

rubocopの設定ファイル
Foo/BarCop:
    Exclude:
        - '**/test/**/*.rb'

testディレクトリではまるっと無視したいcopが時折あると思うのですが、その場合上記のように書けると楽です。
ですが、.rubocopで始まる設定ファイルの場合、
../../../src/**/test/**/*.rbというように適切に../で階層を戻る必要が出てしまいます。
rubocopで始まらない設定ファイルの場合は、
実行ディレクトリからの相対パスなので'**/test/**/*.rb'と簡単にかけます。

.rubocop.ymlをルートに置かない理由

Inculude/Excludeの相対パスがややこしい問題は、.rubocop.ymlをリポジトリルートに置けば解決なので通常はそれで全く問題ないと思っています。

ただ、さまざまな都合でtoolsやconfigディレクトリなどの下にRuboCopの設定ファイルを置きたいケースがあると思っています。

  • 一つのリポジトリの中に複数のアプリがあり、RuboCopの設定は共通にしたい
    • 上の方で例に上げたように、api_server, admin_serverのようケース
    • メインのアプリは一つだが、デプロイ・コード生成・バッチなどのような、メインアプリとは別の小さなアプリ・スクリプトでディレクトリを切っているケース
  • rubocop gemをアプリのGemfileに入れたくない・入れにくい
    • アプリの実行に必要なgemではない
    • 上の方で例に上げたように、api_server, admin_serverのようにアプリが複数あってそれぞれのGemfileに入れるのは微妙感ある
      • かといって、RuboCopのためだけにリポジトリルートに別途Gemfileを用意するのも微妙
  • rubocopをdockerで実行できるようにしておきたい
    • Gemfileにrubocopを入れない場合、各ユーザーがgem install rubocopするのも微妙なのでdockerで起動できるようにしておきたい
    • => dockerfileや便利スクリプトの置き場が必要
  • RuboCopに関するREADMEや設定のガイドラインを用意したい
    • RuboCopの実行の仕方・エディタで動かす方法などを書きたい
    • RuboCopの設定は揉めやすいのでガイドラインとしてドキュメント化しておきたい
    • => 設定ファイル・ドキュメントを一つのディレクトリにまとめたい

さまざまな都合が存在するかなと思います。
チームや状況によってもちろん異なると思いますが、.rubocop.ymlをリポジトリルートに置かないケースはそれなりにあるのかなと思います。

まとめ

inherit_fromに書く相対パス

  • inherit_fromが書かれた設定ファイルからの相対パス

Include/Excludeに書く相対パス

  • それが書かれた設定ファイルが.rubocopで始まるファイル名の場合
    • その設定ファイルからの相対パス
  • 設定ファイル名が.rubocopで始まらない場合
    • rubocopコマンドを実行したディレクトリからの相対パス
  • 設定ファイルをリポジトリルートに置かない場合は、.rubocopで始まらない設定ファイル名のほうが楽

おまけ

https://speakerdeck.com/vividmuimui/anatafalsezhi-ranairubocopfalseshe-ding
今回の話は、社内LTでの内容をリライトしたものです。メイン部分は同じ話ですが、違う設定に関する話もちょっと書いてあるので、よかったら見てください :bow:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails 5 環境構築にDockerを使ってみた。

Qiita初投稿です。
独学でRuby on Railsを勉強しており、今回はDockerを使って環境構築してみました。
もし間違っている部分などありましたら、ご指摘下さい

開発環境

MacOS (Mojave 10.14.4)

Service Version
Docker 18.09.2
docker-compose 1.23.2
Ruby 2.6.2
Ruby on Rails 5.2.3
PostgreSQL 11.2

1.Docker Desktop for Mac のインストール

https://docs.docker.com/docker-for-mac/install/

インストールにはある程度時間がかかります。
Dockerのオフィシャルサイトはもちろん全て英語表記ですので、Google翻訳を使って読み進めてもいいでしょう。
手順通りに設定していくと、クジラが微笑んでくれます。可愛いですね。

2.アプリケーション構築のための各種ファイル設定

開発していくディレクトリにアプリケーションを構築するために必要な4つのファイルを設定します。
今回はUserフォルダ配下にmy_appディレクトリを作成しました。
ファイルがない場合は、コンソール上で$ cd 開発ディレクトリ$ touch Gemfile.lockで対応してください。

  • Dockerfile
  • Gemfile
  • Gemfile.lock
  • docker-compose.yml
Dockerfile
FROM ruby:2.6.2

RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN apt-get install -qy git

RUN mkdir /my_app
WORKDIR /my_app
ADD Gemfile /my_app/Gemfile
ADD Gemfile.lock /my_app/Gemfile.lock

RUN bundle install

COPY . /my_app

RUN apt-get update -qq && apt-get install -y nodejs postgresql-clientの行は、&&を消して

RUN apt-get update -qq
RUN apt-get install -y nodejs postgresql-client

と記述しても大丈夫です。

Gemfile
source 'https://rubygems.org'
gem 'rails', '5.0.0.1'
Gemfile.lock(空ファイル)
docker-compose.yml
version: "3"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/my_app
    ports:
      - "3000:3000"
    depends_on:
      - db

3.プロジェクト・データベースの作成

データベースをPostgreSQLに指定して、これまで設定したファイルを基にRailsプロジェクトを作成します。

terminal
$ docker-compose run web rails new . --force --database=postgresql

Rails関連のファイルが作成されました。
データベースの情報を設定するために、config/database.ymlを変更し、データベースを作成していきます。

config/database.yml(抜粋)
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  host: db
  username: postgres
  password:
  pool: 5
terminal
$ docker-compose run web rake db:create

4.Dockerの起動・停止

最後にコンテナをビルドしてから、Dockerを起動させましょう。

terminal
$ docker-compose build
$ docker-compose up

http://localhost:3000 にアクセスして、Railsプロジェクトが立ち上がっているか確認してみましょう。

image.png

この画面が表示されていれば環境構築完了です。お疲れさまでした!

Dockerを停止する際は以下をコマンドに入力してください。

terminal
$ docker-compose stop

(参考リンク)

Docker オフィシャルページ

Quickstart: Compose and Rails

Qiita記事

Docker入門 #5 【Ruby on Rails5環境構築】
docker-compose コマンドまとめ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでDBに絵文字を保存したい

特に設定しなければ絵文字を書いて保存しようとすると、Incorrect string valueのエラーが発生します。

絵文字を保存する場合は、config/database.ymlのencodingをutfmb4にします。

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

また、テーブルを作る際もCHARSET=utfmb4を設定します。

create_table "test_table", options: "CHARSET=utf8mb4" do |t|
    t.string "name", null: false
  end

すでに、DBを作っている場合は一度DBをdropして、DBから作り直す必要があります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む