20190530のRubyに関する記事は20件です。

今日のリファクタリング -1

お腹ぐるぐるヨーグルト
どうも k1y0です。初めてのqiita投稿。

今日のキニナルコード

before_action :set_content #@content

def show
  if params[:type] == 'programming'
    @title = '速習 プログラミング!'
    @text  = @content.programming_text
  elsif params[:type] == 'food'
    @title = '美味しい焼肉屋さん'
    @text  = @content.food_text
  elsif params[:type] == 'money'
    @title = '爆速で100億稼ぐ方法'
    @text  = @content.money_text
  end
end

こんなコードがありました。

どう変更したか

#before省略
def show
  @title, @text =
  case params[:type]
  when 'programming'
    '速習 プログラミング!', @content.programming_text
  when 'food'
    '美味しい焼肉屋さん', @content.food_text
  when 'money'
    '爆速で100億稼ぐ方法', @content.money_text
  end
end

課題

typeごとにhashとか作ってkeyで受け取った方がわかりやすそう。rubyむずかしい

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

Amazon Linux 2でrbenvを使う

環境

  • Amazon Linux 2

インストール手順

install
# 0. 依存ライブラリのインストール
$ sudo yum install -y git gcc openssl-devel readline-devel zlib-devel
# 1. レポジトリをクローン
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
# 2. .bash_profileにパスを追記
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
# 3. init
$ ~/.rbenv/bin/rbenv init
# 4. Restart your shell so that PATH changes take effect.
$ exec $SHELL -l
# 5. プラグイン用ディレクトリ
$ mkdir -p "$(rbenv root)"/plugins
# 6. rbenv installができるように
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
# 7. 確認
$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

使い方

usage
 # インストールできるバージョンを確認
 $ rbenv install -l

 # 指定バージョンのRubyをインストール
 $ rbenv install 2.5.1

 # グローバルのバージョン指定
 $ rbenv global 2.5.1

 # インストールされているバージョンを確認
 $ rbenv versions

参考

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

【Ruby on Rails】resourcesで作られるルーティングの表記を作るアプリを作った。

タイトル見て「は?」って思った方もいるかなと思います。

Railsでアプリを作成する際に欠かせないルーティングですが、そこには resources っていう便利なメソッドがあります。

例えば、routes.rbに

routes.rb
resources :users

って書くと

Prefix Verb URI Pattern Controller#Action
users  GET /users(.:format) users#index
POST /users(.:format) users#create
new_user  GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE  /users/:id(.:format) users#destroy

これらが一気に作られるわけですよ。(ああ便利)

ただ、たまに resources を使いたくない人とか使えない場合とかあったりなかったりします。

例えば、URI Patteren のところに users ではないコントローラーを使いたいときとか。

そんなとき用に完全個人用に Rails でアプリ作りました。
(個人用にサクッと作ったので見たらわかるやっすいやつやん)

↓↓↓↓↓↓↓↓これです↓↓↓↓↓↓↓↓
https://resourcesrouting.herokuapp.com/

使い方

Name と Name2 にそれぞれ何かしら文字列を入れて "Create Home" ボタン押すだけ。
スクリーンショット 2019-05-30 20.39.57.png

下のように出力されます。
スクリーンショット 2019-05-30 20.40.05.png

あとはコピってペーストするだけ。

正直ワンパターンだけしかこんな今年ないならいらないですが、いくつもルーティングをこんな感じにする際は多少楽になるかなって思います。

後記

実際これ作るのに30分もかかってないですが、こんなの作ってる暇あったら素直に手打ちできたなと思っています(笑)

まぁでも、楽しいんでいいです(¬ω¬)‬b

ちなみにDBもセッションもなにも使ってないので何かが保存されることはないです。

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

Ruby アプリケーションの Cloud Native なモニタリング

What's Cloud Native?

  • Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds.
  • Resilient, Manageable, and Observable
  • たくさんのアプリケーションを動かすことへの問題、はパブリッククラウド や Docker や Kubernetes がかなり解決してくれた
  • それらをいい感じに強調させたり、問題が起きた時に正しく対処を行えるかどうかに大きな課題があるのが現在

Why Cloud Native? (in Quipper case)

  • 一つのコードベースで様々なビジネスが行われ、組織的・ビジネス的な複雑性が課題となっている
    • B2C/B2B, 小中高, 国内/グローバル, そして新規事業
  • サービスがたくさん増えて、複雑なエコシステムを形成しようとしている
    • Microservices
  • 複数のクラスタ、環境上に、様々なプロダクト、アプリケーション
  • SRE がボトルネックにならず、開発チームが自律的かつ高速にプロダクト開発を回せるようにしたい
    • セルフサービス化の流れ
    • Observability は大事だけど、そればかりに時間を取られては本末転倒

The Theree Pillars of Observability

  • https://www.oreilly.com/library/view/distributed-systems-observability/9781492033431/ch04.html
    • Event Logs
      • Quipper では GCP の Stackdriver Logging を使っている (これは超絶便利!!)
    • Metrics
      • Quipper では主に Datadog を使っている
      • 今日はこれの話
    • Tracing
      • Jaeger 試したりしているけどまだまだ活用できてない
      • そもそも社内的にまだそんなに欲されている状況でもない
      • とはいえ必要になるのも時間の問題なので今日の kawasy さんの発表楽しみです!!!

Collecting Ruby Metrics for Datadog

  • 今日は prometheus_exporter という gem について紹介します
  • Ruby プロセスや様々なフレームワーク (Rails, Puma, Sidekiq, etc) のための Prometheus の Exporter を提供する
    • Prometheus? Exporter?

Prometheus

  • 時系列データベースを基盤としたモニタリングシステム
  • が、Quipper で使っている訳ではないので僕も触ったことがある訳ではない
  • そもそも Datadog を使っているのに何故?
    • そこで Exporter

Prometheus Exporter

  • 監視対象のサーバ等で起動して、Prometheus から pull してメトリクスを取得するための Web API みたいなもの
  • オープンソースで多数公開されている
  • Datadog は Prometheus Exporter からのメトリクス収集もできる
# HELP ruby_rss Total RSS used by process.
# TYPE ruby_rss gauge
ruby_rss{type="master",pid="1",pod_name="api-6bcf6c8554-jntdq"} 390942720
ruby_rss{pod_name="api-6bcf6c8554-jntdq",type="web",pid="45"} 346877952
ruby_rss{pod_name="api-6bcf6c8554-jntdq",type="web",pid="42"} 347594752
ruby_rss{pod_name="api-6bcf6c8554-jntdq",type="web",pid="39"} 350285824
ruby_rss{pod_name="api-6bcf6c8554-jntdq",type="web",pid="50"} 347901952

# HELP ruby_unicorn_workers_total Number of unicorn workers.
# TYPE ruby_unicorn_workers_total gauge
ruby_unicorn_workers_total{pod_name="api-6bcf6c8554-jntdq"} 4

# HELP ruby_unicorn_active_workers_total Number of active unicorn workers
# TYPE ruby_unicorn_active_workers_total gauge
ruby_unicorn_active_workers_total{pod_name="api-6bcf6c8554-jntdq"} 0

Datadog and Kubernetes

  • Datadog には Auto Discovery という仕組みがあり、Pod (コンテナの集合的なやつ) の annotations (メタデータみたいなやつ) に設定を記述するだけで、Datadog が勝手に収集してくれる
    • コピペでメトリクスの収集が行われるようになるので簡単
    • サービス作成時のテンプレート的なやつに含めて自動化とかもできそう
  • Kubernetesにデプロイしたアプリケーションのメトリクスを自動収集する

Annotation

metadata:
  annotations:
    ad.datadoghq.com/api.check_names: |
      ["prometheus"]
    ad.datadoghq.com/api.init_configs: |
      [{}]
    ad.datadoghq.com/api.instances: |
      [
        {
          "prometheus_url": "http://%%host%%:9394/metrics",
          "namespace": "prometheus_checks",
          "metrics": ["*"]
        }
      ]

今日の成果

image.png


Ruby クイズ: 何が問題だったでしょう

元のコード

    def worker_process_count
      # ...

      # find all processes whose parent is the unicorn master
      # but we're actually only interested in the number of processes (= lines of output)
      result = `ps --no-header -o pid --ppid #{pid}`
      result.lines.count
    end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】Active Storage + S3 + Active JobでGoogle Cloud Visionセーフサーチ

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

Amazon S3に画像をダイレクトアップロード

Railsガイドをもとに実装しました。

セーフサーチを実装

前回の記事のとおりです。

【Rails】Active Storage環境下でGoogle Cloud Visionのセーフサーチを実装

S3アップロード画像のパブリックURLを取得

こちらの記事を参考にしました。

Rails ActiveStorage で PUBLIC な URL を表示する

セーフサーチを非同期処理化

※controllerのコードは省略します

model

post.rb
    has_one_attached :image
    after_commit :annotate_self, on: [:create, :update]

    def annotate_self
        if image.attached?
            ImageAnnotateJob.set(wait: 10.second).perform_later(self)
        end
    end

postの新規投稿もしくは編集のときに画像imageが添付されていたらImageAnnotateJobというActive Jobのキューを走らせます。10秒後に走らすようセットした理由は、Amazon S3へのアップロード完了までのタイムラグが発生するかなと思ったからです。

job

image_annotate_job.rb
class ImageAnnotateJob < ApplicationJob
  queue_as :second

  def perform(target)

    tempfile = target.image.attachment.service.send(:object_for, target.image.key).public_url

    require "google/cloud/vision"
    image_annotator = Google::Cloud::Vision::ImageAnnotator.new
    response = image_annotator.safe_search_detection image: tempfile
    response.responses.each do |res|
    safe_search = res.safe_search_annotation
      if safe_search.adult.to_s == "VERY_LIKELY" || safe_search.adult.to_s == "LIKELY"
        target.destroy
        return
      elsif safe_search.violence.to_s == "VERY_LIKELY" || safe_search.violence.to_s == "LIKELY"
        target.destroy
        return
      elsif safe_search.medical.to_s == "VERY_LIKELY" || safe_search.medical.to_s == "LIKELY"
        target.destroy
        return
      end
    end

  end

end

いったんtempfileにS3アップロード画像のパブリックURLを格納。それをGoogle Cloud Visionのセーフサーチにかけます。このコードはすべてActive Jobによってバックグラウンド(非同期)処理されます。もし不適切な画像と判断されれば投稿そのものを削除する流れです。

Sidekiq

config\sidekiq.yml
:concurrency: 5
:queues:
  - [default, 7]
  - [second, 5]

じぶんはActive JobのライブラリとしてSidekiqを使用しています。上記image_annotate_job.rbqueue_as :secondによってキューが処理される優先順位を設定しました。

あとがき

駆け足ですが以上です。Google Cloud Visionのセーフサーチは高精度ですがレスポンスに1~2秒ほど要するため、そのぶん前回の記事のような同期処理だと投稿完了までにユーザーを待たせてしまううえ、サーバーサイドに負荷がかかります。今回の記事はそれらの課題を解決するものとなります。

また、苦労したのがS3アップロード画像のパブリックURLを取得する部分でした。url_forrails_blob_urlといったActive Storageのメソッドを使うと時限的なものorリダイレクトされるURLしか取得できずセーフサーチにかけても「そんな画像はありませんよ」と言われてしまいます。

というわけで試行錯誤を経たおかげでよりレスポンス性とセキュリティ性を兼ね備えた画像投稿機能を実装できました。

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

The Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrs

The Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrsThe Indian (Black Magic Astrologer) World Famous Vashikaran Expert{+91-7837546443} #Online24hrs[uploading-0]()

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

Travis CIからSlackに通知する際の認証情報の暗号化(travis encrypt)について

TL;DR

  1. .travis.ymlに直トークン書きたくないからtravis encryptしたい
  2. Travis「Travis CLI 使ってね」っ gem install travis
  3. このプロジェクトrubyじゃないし、ローカルにもruby入ってない…

というときに、rubyのDockerイメージを使ってアプリケーションに影響なくサクッとやりたい

準備

実践したのはmac + Docker for Mac環境ですが、Docker使えれば大丈夫なはずです

  • macOS 10.14.5
  • docker 18.09.2

想定プロジェクト

  • GitHubで管理されている
  • travis-ci.comでCIを回している
  • アプリケーションルートに.travis.ymlがある

手順

$ cd /path/to/project
$ docker run -it --rm --volume $(pwd)/:/app ruby bash
# cd app
# gem install travis
# travis login --com --github-token XXXXXXXX
# travis encrypt "xxxxxx:XXXXXXXX" --add notifications.slack --com
# exit

--comオプションについて

travis-ci.comを使ってるので--comオプションつけましたが、travis-ci.orgな場合はオプション無しでよいのかもしれません(未検証)

--github-tokenオプションについて

GitHubで二段階認証を有効化している場合は、Personal access tokenを発行して指定する必要があります
GitHub > Settings > Developer settings > Personal access tokens から発行できます
必要なスコープはrepo, user:emailだと思われます
有効化していない場合は--github-tokenオプションなしでやれば、普通にuser/passの認証がされるのでそれで大丈夫だと思われます(未検証)

travis encryptコマンドの部分はSlackのTravisアプリ画面の「セットアップの手順」に書いてあるのでコピーしちゃえば大丈夫です

上記うまくいけば.travis.ymlに暗号化された認証情報が追記されているはずなので、コミットしちゃえばOK

+notifications:
+  slack:
+    secure: XXXXXXXXXXXXXXXX...

まとめ

Circle CIは設定ファイルじゃなくWeb上の設定画面でWebhookのURL指定するだけで通知できたので楽ちんだった

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

Tmp

demo

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

+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}

+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}+91-9501761003 Black magic Specialist Molvi ji in {{Mumbai}}

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

mac で rails new した後の bundle install 時に ffi 1.11.0 のインストールが失敗したのでバージョン固定で解決

バージョン

名前 バージョン
macOS 10.13.6
ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin17]
rails 5.2.3

起きたエラー内容

 $ bundle exec rails new ../sample
      create  
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
      create  Gemfile

(略)


Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Fetching rake 12.3.2


Your user account isn't allowed to install to the system RubyGems.
  You can cancel this installation and run:

      bundle install --path vendor/bundle

  to install the gems into ./vendor/bundle/, or you can enter your password
  and install the bundled gems to RubyGems using sudo.

  Password: 
Installing rake 12.3.2
Fetching concurrent-ruby 1.1.5
Installing concurrent-ruby 1.1.5

(略)

Fetching coffee-rails 4.2.2
Installing coffee-rails 4.2.2
Fetching ffi 1.11.0
Installing ffi 1.11.0 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c
/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/bin/ruby -r ./siteconf20190519-47252-1at7a63.rb extconf.rb
checking for ffi.h... no
checking for ffi.h in /usr/local/include,/usr/include/ffi... yes
checking for ffi_call() in -lffi... yes
checking for ffi_closure_alloc()... no
checking for shlwapi.h... no
checking for rb_thread_call_without_gvl()... yes
checking for ruby_native_thread_p()... yes
checking for ruby_thread_has_gvl_p()... yes
creating extconf.h
creating Makefile

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/extensions/universal-darwin-17/2.3.0/ffi-1.11.0/mkmf.log

current directory: /private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c
make "DESTDIR=" clean

current directory: /private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c
make "DESTDIR="
mkdir -p "/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386; (if [ ! -f
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi"/configure ]; then echo "Running autoreconf for libffi"; cd
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi" && /bin/sh
/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi/autogen.sh > /dev/null; fi); (if [ ! -f
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386/Makefile ]; then echo "Configuring libffi for i386"; cd
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386 && env CC=" xcrun clang" CFLAGS="-arch i386 " LDFLAGS="-arch i386"
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi"/configure --disable-static --with-pic=yes --disable-dependency-tracking --disable-docs
--host=i386-apple-darwin > /dev/null; fi); env MACOSX_DEPLOYMENT_TARGET=10.4 /Applications/Xcode.app/Contents/Developer/usr/bin/make -C
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386

(略)

libtool: link: ranlib .libs/libffi_convenience.a
libtool: link: ( cd ".libs" && rm -f "libffi_convenience.la" && ln -s "../libffi_convenience.la" "libffi_convenience.la" )
/bin/sh ./libtool  --tag=CC   --mode=link xcrun clang  -arch i386  -Wall -fexceptions -no-undefined -version-info `grep -v '^#'
/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi/libtool-version`   '-arch' 'i386'  -arch i386 -o libffi.la -rpath /usr/local/lib src/prep_cif.lo
src/types.lo src/raw_api.lo src/java_raw_api.lo src/closures.lo  src/x86/ffi.lo src/x86/sysv.lo
libtool: link:  xcrun clang -dynamiclib  -o .libs/libffi.7.dylib  src/.libs/prep_cif.o src/.libs/types.o src/.libs/raw_api.o src/.libs/java_raw_api.o src/.libs/closures.o src/x86/.libs/ffi.o src/x86/.libs/sysv.o    -arch i386 -arch
i386 -arch i386   -install_name  /usr/local/lib/libffi.7.dylib -compatibility_version 9 -current_version 9.0 -Wl,-single_module
ld: warning: The i386 architecture is deprecated for macOS (remove from the Xcode build setting: ARCHS)
Undefined symbols for architecture i386:
  "___x86.get_pc_thunk.bx", referenced from:
      _ffi_call_i386 in sysv.o
      _ffi_closure_raw_SYSV in sysv.o
      _ffi_closure_raw_THISCALL in sysv.o
  "___x86.get_pc_thunk.dx", referenced from:
      _ffi_closure_i386 in sysv.o
      _ffi_closure_STDCALL in sysv.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[3]: *** [libffi.la] Error 1
make[2]: *** [all-recursive] Error 1
make[1]: *** [all] Error 2
make: *** ["/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386/.libs/libffi_convenience.a] Error 2

make failed, exit code 2

Gem files will remain installed in /var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0 for inspection.
Results logged to /var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/extensions/universal-darwin-17/2.3.0/ffi-1.11.0/gem_make.out

An error occurred while installing ffi (1.11.0), and Bundler cannot continue.
Make sure that `gem install ffi -v '1.11.0'` succeeds before bundling.

In Gemfile:
  spring-watcher-listen was resolved to 2.0.1, which depends on
    listen was resolved to 3.1.5, which depends on
      rb-inotify was resolved to 0.10.0, which depends on
        ffi
         run  bundle exec spring binstub --all
bundler: command not found: spring
Install missing gem executables with `bundle install`
$

macOS が 10.13.6 なのがきっといけないのだろうと思うが、そこじゃない場所で解決を試みる。

解決後に Gemfile.lock を見ると、 ffi の依存指定は一箇所のみで、こうなっていた。

    rb-inotify (0.10.0)                                                                                                
      ffi (~> 1.0)

ffi (~> 1.0) とあるので、 ffi1.x の最新版を取ろうとして、それがインストールできていない。

解決方法

~> 1.0 ということは 1.11.0 が必要なわけじゃないはずので、 Gemfile を書き換えて、インストールできるバージョンに固定した。 1.9.21 は、あくまで例です

gem 'ffi', '= 1.9.21'

(これは要らないかもしれない) 事前にインストールしておく。

$ sudo gem install ffi -v '1.9.21'
Fetching: ffi-1.9.21.gem (100%)
Building native extensions.  This could take a while...
Successfully installed ffi-1.9.21
Parsing documentation for ffi-1.9.21
Installing ri documentation for ffi-1.9.21
Done installing documentation for ffi after 3 seconds
1 gem installed
$

で、改めて bundle install

$ bundle install --path vendor/bundle
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 12.3.2
Using concurrent-ruby 1.1.5

(略)

Using railties 5.2.3
Using coffee-rails 4.2.2
Fetching ffi 1.9.21
Installing ffi 1.9.21 with native extensions

うまくいった

バージョンの選び方

ffi 1.9.21 を選ぶべきだというわけではないのですが、あらかじめ mac 側にインストールしてある可能性(他の ruby プログラムを動かすためにインストールしたことがあった、など)を考えれば、たとえば gem list で調べればインストール可能なバージョンがわかるはず

$ gem list ffi

*** LOCAL GEMS ***

ffi (1.9.21)
public_suffix (3.0.3)

それ以外では、 https://rubygems.org/gems/ffi/versions を見て、一つ前のバージョンの最新を選ぶと良いと思います。今回だと、失敗したのは 1.11.0 なので、 1.10.x の中での最新の 1.10.0 を試す、それでもダメなら 1.9.25 を試す、...を繰り返す、など。

時間がある人は、もちろん原因を特定してそれが解決できる最新のバージョンを選びましょう。

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

mac で (rails new した後の bundle install 時に) ffi 1.11.0 のインストールが失敗したのでバージョン固定で解決

バージョン

名前 バージョン
macOS 10.13.6
ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin17]
rails 5.2.3

コメントでご指摘あったように、問題そのものは Rails とは関係ないです。

起きたエラー内容

 $ bundle exec rails new ../sample
      create  
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
      create  Gemfile

(略)


Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Fetching rake 12.3.2


Your user account isn't allowed to install to the system RubyGems.
  You can cancel this installation and run:

      bundle install --path vendor/bundle

  to install the gems into ./vendor/bundle/, or you can enter your password
  and install the bundled gems to RubyGems using sudo.

  Password: 
Installing rake 12.3.2
Fetching concurrent-ruby 1.1.5
Installing concurrent-ruby 1.1.5

(略)

Fetching coffee-rails 4.2.2
Installing coffee-rails 4.2.2
Fetching ffi 1.11.0
Installing ffi 1.11.0 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c
/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/bin/ruby -r ./siteconf20190519-47252-1at7a63.rb extconf.rb
checking for ffi.h... no
checking for ffi.h in /usr/local/include,/usr/include/ffi... yes
checking for ffi_call() in -lffi... yes
checking for ffi_closure_alloc()... no
checking for shlwapi.h... no
checking for rb_thread_call_without_gvl()... yes
checking for ruby_native_thread_p()... yes
checking for ruby_thread_has_gvl_p()... yes
creating extconf.h
creating Makefile

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/extensions/universal-darwin-17/2.3.0/ffi-1.11.0/mkmf.log

current directory: /private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c
make "DESTDIR=" clean

current directory: /private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c
make "DESTDIR="
mkdir -p "/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386; (if [ ! -f
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi"/configure ]; then echo "Running autoreconf for libffi"; cd
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi" && /bin/sh
/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi/autogen.sh > /dev/null; fi); (if [ ! -f
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386/Makefile ]; then echo "Configuring libffi for i386"; cd
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386 && env CC=" xcrun clang" CFLAGS="-arch i386 " LDFLAGS="-arch i386"
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi"/configure --disable-static --with-pic=yes --disable-dependency-tracking --disable-docs
--host=i386-apple-darwin > /dev/null; fi); env MACOSX_DEPLOYMENT_TARGET=10.4 /Applications/Xcode.app/Contents/Developer/usr/bin/make -C
"/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386

(略)

libtool: link: ranlib .libs/libffi_convenience.a
libtool: link: ( cd ".libs" && rm -f "libffi_convenience.la" && ln -s "../libffi_convenience.la" "libffi_convenience.la" )
/bin/sh ./libtool  --tag=CC   --mode=link xcrun clang  -arch i386  -Wall -fexceptions -no-undefined -version-info `grep -v '^#'
/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c/libffi/libtool-version`   '-arch' 'i386'  -arch i386 -o libffi.la -rpath /usr/local/lib src/prep_cif.lo
src/types.lo src/raw_api.lo src/java_raw_api.lo src/closures.lo  src/x86/ffi.lo src/x86/sysv.lo
libtool: link:  xcrun clang -dynamiclib  -o .libs/libffi.7.dylib  src/.libs/prep_cif.o src/.libs/types.o src/.libs/raw_api.o src/.libs/java_raw_api.o src/.libs/closures.o src/x86/.libs/ffi.o src/x86/.libs/sysv.o    -arch i386 -arch
i386 -arch i386   -install_name  /usr/local/lib/libffi.7.dylib -compatibility_version 9 -current_version 9.0 -Wl,-single_module
ld: warning: The i386 architecture is deprecated for macOS (remove from the Xcode build setting: ARCHS)
Undefined symbols for architecture i386:
  "___x86.get_pc_thunk.bx", referenced from:
      _ffi_call_i386 in sysv.o
      _ffi_closure_raw_SYSV in sysv.o
      _ffi_closure_raw_THISCALL in sysv.o
  "___x86.get_pc_thunk.dx", referenced from:
      _ffi_closure_i386 in sysv.o
      _ffi_closure_STDCALL in sysv.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[3]: *** [libffi.la] Error 1
make[2]: *** [all-recursive] Error 1
make[1]: *** [all] Error 2
make: *** ["/private/var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0/ext/ffi_c"/libffi-i386/.libs/libffi_convenience.a] Error 2

make failed, exit code 2

Gem files will remain installed in /var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/gems/ffi-1.11.0 for inspection.
Results logged to /var/folders/_h/xgr__5xn3kscjx0f8m7v3qbr0000gn/T/bundler20190519-47252-1smbwrlffi-1.11.0/extensions/universal-darwin-17/2.3.0/ffi-1.11.0/gem_make.out

An error occurred while installing ffi (1.11.0), and Bundler cannot continue.
Make sure that `gem install ffi -v '1.11.0'` succeeds before bundling.

In Gemfile:
  spring-watcher-listen was resolved to 2.0.1, which depends on
    listen was resolved to 3.1.5, which depends on
      rb-inotify was resolved to 0.10.0, which depends on
        ffi
         run  bundle exec spring binstub --all
bundler: command not found: spring
Install missing gem executables with `bundle install`
$

macOS が 10.13.6 なのがきっといけないのだろうと思うが、そこじゃない場所で解決を試みる。 コメントにて、OS も関係ないとのご指摘。 ld: warning: The i386 architecture is deprecated for macOS (remove from the Xcode build setting: ARCHS) の辺りを調べればいいかもしれないのですが、それはまたの機会に。

解決後に Gemfile.lock を見ると、 ffi の依存指定は一箇所のみで、こうなっていた。

    rb-inotify (0.10.0)                                                                                                
      ffi (~> 1.0)

ffi (~> 1.0) とあるので、 ffi1.x の最新版(当時は 1.11.0 )を取ろうとして、それがインストールできていない。

解決方法

~> 1.0 ということは 1.11.0 が必要なわけじゃないはずので、 Gemfile を書き換えて、インストールできるバージョンに固定した。( 1.9.21 は、あくまで例です)

gem 'ffi', '= 1.9.21'

(これは要らないかもしれない) 事前にインストールしておく。

$ sudo gem install ffi -v '1.9.21'
Fetching: ffi-1.9.21.gem (100%)
Building native extensions.  This could take a while...
Successfully installed ffi-1.9.21
Parsing documentation for ffi-1.9.21
Installing ri documentation for ffi-1.9.21
Done installing documentation for ffi after 3 seconds
1 gem installed
$

で、改めて bundle install

$ bundle install --path vendor/bundle
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 12.3.2
Using concurrent-ruby 1.1.5

(略)

Using railties 5.2.3
Using coffee-rails 4.2.2
Fetching ffi 1.9.21
Installing ffi 1.9.21 with native extensions

うまくいった

バージョンの選び方

ffi 1.9.21 を選ぶべきだというわけではないのですが、あらかじめ mac 側にインストールしてある可能性(他の ruby プログラムを動かすためにインストールしたことがあった、など)を考えれば、たとえば gem list で調べればインストール可能なバージョンがわかるはず

$ gem list ffi

*** LOCAL GEMS ***

ffi (1.9.21)
public_suffix (3.0.3)

それ以外では、 https://rubygems.org/gems/ffi/versions を見て、一つ前のバージョンの最新を選ぶと良いと思います。今回だと、失敗したのは 1.11.0 なので、 1.10.x の中での最新の 1.10.0 を試す、それでもダメなら 1.9.25 を試す、...を繰り返す、など。

時間がある人は、もちろん原因を特定してそれが解決できる最新のバージョンを選びましょう。

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

find_or_initialize_byメソッド

find_or_initialize_byメソッドはfind_or_create_byと同様に動作しますが、createの代りにnewを呼ぶ点が異なります。つまり、モデルの新しいインスタンスは作成されますが、その時点ではデータベースに保存されていません。

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

【Rails】個人開発のアプリに5,000万円の交渉相手が現れました

個人開発のWebアプリまちかどルート

驚いたことに5,000万円の交渉話が飛び込んできました。

まだ交渉中ではありますが、ここに至る1年弱の所感をまとめたいと思いました。

RubyとRailsに入門して1年弱

プログラミングを学んだのは、40代でセミリタイア&山奥で隠居生活するようになってからというもの、新たなたしなみのひとつにしようと思ったのがきっかけです。

ソーシャルな隠居の、新たな“たしなみ”。プログラミングをはじめました

アイデアがどう生まれたか

世間と隔絶されずより一層ひととの関係性を高める自称 ソーシャルな隠居生活では、じぶんの時間がたくさん持てます。余白の時間に「あ、こんな機能を作ってみようかな」というアイデアがふと生まれやすいです。

アイデアとは、ひねり出すものではなく、それまで興味の赴くまま得てきた数多くのインプットと行動から余白の時間にふと思いつくものなのでしょうか。

よくローカル環境構築などと言いますが、ノートPC上だけでなく自身の環境構築も大事なんだと思いました。そういう意味ではじぶんの環境構築は恵まれていたかもしれません。

生活への変化

プログラミングをたしなみのひとつにしたおかげで、そうした余白の時間が充実したものになりました。

たとえばカフェなどで思いがけず待ち時間が長くなったとしても、ふとアイデアを思いついたらノートPCを開き、コードに落とし込みます。

いままで持て余していた時間が楽しいものとなりました。

どう学んできたか

書籍を読んだりスクールに通うということをしていません。ほとんどが必要に応じてインターネットで調べるという独学スタイルです。そのせいか基本的な知識がところどころ抜けているように思います。

反面、基本的な知識がないおかげなのか、参考文献をそのまま写すのではなく「もしかしてこんなふうに書けば動くのかな」という感じでコードを自由奔放に書き換えたりするんですけど、それが本当に動くとうれしいですね。いわゆる論理的思考が鍛えられているのでしょうか。

開発を続けられた理由

オープンソースのSNS「Mastodon」に触れていたことが大きかったと思います。だいぶお手本にさせていただきましたし、APIで連携することで個人開発の幅が広がりました。

Mastodonが日本に普及して2年。インスタンス運営と連携アプリの開発が楽しい

これから

5,000万円の交渉の成否はこれからのことなのでさておき、じぶんの生活を経済的というよりひととの関係性や精神的に豊かにするための趣味の個人開発ですから飽きるまで続けていこうと思います。

なによりも、スタートアップやエンジニア関係のつながりがこれまで以上に増えたことがうれしいです。やはりコミュニティとして日常的に学びあうことが、成長のスピードを加速させるんでしょうね。

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

[初心者]テストコードを書く際の原則

テストコードとは?

テストコードとは、本番環境に実装する前に、各メソッドがしっかりと自分が期待した値を取得し、保存しているのか確認することを指します。

もしなにもテストせずに本番環境に実装してしまったら、ユーザーからの苦情があり、サーバーを止めて修正しないといけないですよね??
それらの原因が結果的に、損失だったり、ユーザーの評価を下げてしまうことになる可能性があります?

今回はテストコードを書く際の大切な原則について書いていきたいと思います!!

①各exampleで期待する値は1つだけ

期待する値を2つ以上含めてしまうと、どちらのエクスペクテーションでエラーが出たのか判別できず、正確なテストができないため、1つだけにしておきましょう!!

※エクスペクテーション(予想、期待、切望、待望)

②期待する結果をはっきりと分かりやすく記述

明快な記述にすることで、自身の確認やメンバーとの共有、顧客への仕様説明が楽になります。
結果としてコミュニケーションミスが低下し、スムーズに仕事を進めることができます。

もし分かりづらい記述だったら、一々相手に確認をしてテストするという無駄な時間が生まれますよね?

③起きて欲しいことと起きてほしくないこと両方をテストする

これはどちらもテストで予期せぬ動作が残るのを防ぐためにどっちも行います!!

そうすればエラー発生率の低下に繋がると思っています?

④境界値をテストする

例えば、パスワード登録の際に8文字以上を保存するとしましょう。
テストする際には、8文字以上保存されているかの確認と、逆に7文字以下のパスワードは保存されていないかテストをする必要があります。
よくバリデーションとかに使われるので、しっかりチェックする必要がありますね?

可読性を考えつつ、適度にDRYする

DRYとはDon't Repeat Yourselfの略になります。
テストコードにおいて何よりも相手に伝わる分かりやすさが大切です。
分かりづらくなってテストの見落としが起きるよりマシだと思いませんか?

最後に

本番環境前には必ずテストコードを書き、
ちゃんとメソッドが定義されているのか、
値がしっかり取得されているのかを確認してしっかりとしたアプリケーションを作っていきましょう!!!

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

【スクール生】12日目:テストコードを書く際の原則

テストコードとは?

テストコードとは、本番環境に実装する前に、各メソッドがしっかりと自分が期待した値を取得し、保存しているのか確認することを指します。

もしなにもテストせずに本番環境に実装してしまったら、ユーザーからの苦情があり、サーバーを止めて修正しないといけないですよね??
それらの原因が結果的に、損失だったり、ユーザーの評価を下げてしまうことになる可能性があります?

今回はテストコードを書く際の大切な原則について書いていきたいと思います!!

①各exampleで期待する値は1つだけ

期待する値を2つ以上含めてしまうと、どちらのエクスペクテーションでエラーが出たのか判別できず、正確なテストができないため、1つだけにしておきましょう!!

※エクスペクテーション(予想、期待、切望、待望)

②期待する結果をはっきりと分かりやすく記述

明快な記述にすることで、自身の確認やメンバーとの共有、顧客への仕様説明が楽になります。
結果としてコミュニケーションミスが低下し、スムーズに仕事を進めることができます。

もし分かりづらい記述だったら、一々相手に確認をしてテストするという無駄な時間が生まれますよね?

③起きて欲しいことと起きてほしくないこと両方をテストする

これはどちらもテストで予期せぬ動作が残るのを防ぐためにどっちも行います!!

そうすればエラー発生率の低下に繋がると思っています?

④境界値をテストする

例えば、パスワード登録の際に8文字以上を保存するとしましょう。
テストする際には、8文字以上保存されているかの確認と、逆に7文字以下のパスワードは保存されていないかテストをする必要があります。
よくバリデーションとかに使われるので、しっかりチェックする必要がありますね?

可読性を考えつつ、適度にDRYする

DRYとはDon't Repeat Yourselfの略になります。
テストコードにおいて何よりも相手に伝わる分かりやすさが大切です。
分かりづらくなってテストの見落としが起きるよりマシだと思いませんか?

最後に

本番環境前には必ずテストコードを書き、
ちゃんとメソッドが定義されているのか、
値がしっかり取得されているのかを確認してしっかりとしたアプリケーションを作っていきましょう!!!

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

Rails6 のちょい足しな新機能を試す27(perform_deliveries 編)

はじめに

Rails 6 に追加されそうな新機能を試す第27段。 今回は、 perform_deliveries 編です。
perform_deliveries の値が false(nil) のとき、log への出力メッセージがメールの送信をスキップしたことを示す内容に変わりました。

今回は、User を登録するときに perform_deliveries の値を画面から指定できるようにする(メールを送信する、しないを指定できるようにする)ことにより、動作を確認します。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

$  rails --version
Rails 6.0.0.rc1

Rails プロジェクトを作る

$ rails new rails6_0_0rc1
$ cd rails6_0_0rc1

User の CRUD を作る

User の CRUD を作ります。

$ rails g scaffold User name

UserMailer を作る

メールを送信できるように、UserMailer を作ります。

$ rails g mailer UserMailer

View を変更する

_form.html.erbperform_deliveries を true にするか false にするか指定するチェックボックスを追加します。

app/views/users/_form.html.erb
<%= form_with(model: user, local: true) do |form| %>
  ...
  <div class="field">
    <%= label_tag :perform_deliveries %>
    <%= check_box_tag :perform_deliveries %>
  </div>
  ...
<% end %>

User controller を変更する

UsersController#create メソッドを変更し、DBへの保存が成功したら、 メールを送信するようにします。
このとき、perform_deliveries の値をパラメータとして UserMailer に渡すようにします。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  ...
  def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        perform_deliveries = params[:perform_deliveries].to_i == 1
        UserMailer.with(user: @user, perform_deliveries: perform_deliveries).created_email.deliver_later
        format.html { redirect_to @user, notice: 'User was successfully created.' }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end
  ...
end

以下の2行が追加した部分になります。

        perform_deliveries = params[:perform_deliveries].to_i == 1
        UserMailer.with(user: @user, perform_deliveries: perform_deliveries).created_email.deliver_later

このあとは、 UserMailer 側を修正していきます。

UserMailer クラスにメソッドを追加する

メールを送信する created_email メソッド と perform_deliveries を設定するコールバックを追加します。

app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  after_action :set_perform_deliveries

  def created_email
    @user = params[:user]
    mail(to: 'admin@example.com', subject: 'user created')
  end

  private

  def set_perform_deliveries
    mail.perform_deliveries = params[:perform_deliveries]
  end
end

メールのテンプレートを作成する

今回は手抜きで テキストメールのみとし、 HTML メールは作成しません。

app/views/user_mailer/created_email.text.erb
User created
=============

User <%= @user.name %> was created

メールをファイルに保存する

今回は実際にメールを送信せず、ファイルに保存するように設定を変更します。

config/environments/development.rb
Rails.application.configure do
  ...
  config.action_mailer.delivery_method = :file
  ...
end

db:migrate を実行する

$ bin/rails db:create db:migrate

rails server を実行してブラウザからUserを登録する

User を登録するときに、 perform_deliveries チェックボックスをチェックします。
ユーザーが無事、登録できれば、メールは、tmp/mails ディレクトリ内に保存されます。
このときログを確認すると

log/development.log
... Delivered mail 5ce9f3619b8f4_2f32ae97743a3b0258ac@8baf8e28fbad.mail (6.1ms)

とメールが送信されたことが記録されています。

今度は、perform_deliveries チェックボックスをチェックしないで User を登録します。
tmp/mails ディレクトリの admin@example.com ファイルには何も追加されません。
このときログを確認すると Delivered とはならずに

log/development.log
... Skipped delivery of mail 5ce9f380b1ad6_2f32ae97743a3b025957@8baf8e28fbad.mail as `perform_deliveries` is false

とメールの送信をスキップしたことがログに記録されます。 (←これが今回のちょい足し機能です。)

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try027_perform_deliveries

参考情報

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

ReactやVueなどのJavascriptフレームワークからRailsの多対多モデルに対して、関連付けも含めて一気に作ってみた

こんにちは!@hairgaiです。
突然ですが、とあるTeamを作成するときにMemberを一緒に所属させたい!と思うことって一日に一回くらいありませんか?
今回は、それを実装するときにJavascriptからRailsへのパラメータで若干悩んだので、それを共有しようかなと思います。

前提

よくある多対多です。

  • Teamモデル
    • has_many :team_members
    • has_many :members, through: :team_members
  • TeamMemberモデル
    • belongs_to :team
    • belongs_to :member
  • Memberモデル
    • has_many :team_members: :destroy
    • has_many :teams, through: :team_members

React 16.8.6、Rails 5.2.2

実装

Memberが数人いて何かTeamを作るときに、一度作成して後から所属させるのは面倒なので、当然ながら「Teamを作るときにMemberを所属させつつ作れたらいいのに」と思うと思います。
なので、フロントでTeamを作成するときに、既に登録済みのMemberを一覧で表示して、それを選択させることでパラメータを同時に送らせることにします。

image.png

こんな感じですね。

また、Railsでは、モデルを作成するときに、その多対多関係にあるモデルを一緒に関連付けることが可能です。
なので、上記のように「Teamを作るときにMemberを所属させつつ作れたらいいのに」と思ったら、

pry(main)> Member.create!(email: 'a@example.com')

=> #<Member:0x00007fb1498f04d8
  id: 1,
  email: "a@example.com",
  # ...

pry(main)> team = Team.create!(name: 'hoge', member_ids: [1])

=> #<Team:0x00007fb142722860
  id: 1,
  name: "hoge",
  # ...

pry(main)> team.members

=> [#<Member:0x00007fb149951af8
  id: 1,
  email: "a@example.com",
  # ...
  ]

のように、[モデル名]_idsというパラメータに配列を渡すと関連付けることが出来ます。
なので、フロント側でこのパラメータが渡ってくるようにURLパラメータを生成すれば良いということですね。

パラメータ送りたい

ググると色々な記事が出てくる通り、JavascriptからURLにパラメータ用のクエリを乗っけるときは、qsを使いました。
qsは、Javascriptの連想配列(Hash)からURLパラメータを生成してくれるライブラリで、例えば

import qs from 'qs'; // or var qs = require('qs');

const params = { team: { name: 'hoge' }};
qs.stringify(params) // => 'team%5Bname%5D=hoge'

という感じで使います。
なので(今回はパラメータの話なので解説しませんが)、フロント側でいい感じにパラメータを連想配列として生成します。

// 実際はフレームワーク上でユーザの入力に合わせて作りますが、下記のような連想配列が生成されます。
const params = {
  team: {
    name: 'hoge',
    member_ids: [2, 3]
  }
}

qs.stringify(params) // => 'team%5Bname%5D=hoge&team%5Bmember_ids%5D%5B0%5D=2&team%5Bmember_ids%5D%5B1%5D=3'

これをライブラリ等を用いてRailsに送ってみると…(axiosを使いました)

    15: def update
    16:   binding.pry
 => 17:   @team.update!(team_params)
    # ...

[1] pry(#<Api::V1::TeamsController>)> params
=> <ActionController::Parameters {"team"=>{"id"=>"1", "name"=>"hoge", "member_ids"=>{"0"=>"1", "1"=>"2", "2"=>"3"}}, "format"=>"json", "controller"=>"api/v1/teams", "action"=>"update", "id"=>"1"} permitted: false>

あれ?なんか変な形で送られてますね。

クエリの生成方法のオプションがあった

qsが生成したURLパラメータをデコードしてみると…

team[name]=hoge&team[member_ids][0]=2&team[member_ids][1]=3

ということで、team[member_ids][0]=2&team[member_ids][1]=3のように指定されています。これをRailsでは"member_ids"=>{"0"=>"2", "1"=>"3"}と解釈してしまうようですね。(確かにHashっぽいです)
では、Railsではどう記述すればArrayと解釈されるのか、ということで、逆にArrayからクエリを生成してみましょう。

pry(main)> { name: 'hoge', member_ids: [2, 3] }.to_query('team')
=> "team%5Bmember_ids%5D%5B%5D=2&team%5Bmember_ids%5D%5B%5D=3&team%5Bname%5D=hoge"

# => team[member_ids][]=2&team[member_ids][]=3&team[name]=hoge

順番は違いますが、若干指定の仕方が違いますね。Arrayのクエリでは、[]のように数字を入れずに指定すれば良いようです。
現在はその数字の処理はqs側にまかせています。qsのGitHubをよく読んでみると…

You may use the arrayFormat option to specify the format of the output array:

qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' })
// 'a=b,c'

とのことです。
なので、フロント側でURLパラメータを生成する際に、

const params = {
  team: {
    name: 'hoge',
    member_ids: [2, 3]
  }
}

qs.stringify(params, { arrayFormat: 'brackets' }) // => 'team%5Bname%5D=hoge&team%5Bmember_ids%5D%5B%5D=2&team%5Bmember_ids%5D%5B%5D=3'

とオプションを追加してみると…

[1] pry(#<Api::V1::TeamsController>)> params
=> <ActionController::Parameters {"team"=>{"id"=>"1", "name"=>"hoge", "member_ids"=>["2", "3"]}, "format"=>"json", "controller"=>"api/v1/teams", "action"=>"update", "id"=>"1"} permitted: false>

出来ました!レコードも正常に作られます。

結論

Railsは、Railsが提供するWayに沿えば非常に強力なフレームワークですが、その分色々な決まりがあるので、こういった細かいところもキチンと調べていきたいですね。

この記事が誰かの参考になれば幸いです。

参考

ありがとうございました!

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

~勉強会~ヘルスケアwebサービスを自分で作る医者の日記

やっぱ勉強会に参加は有意義
1人でやると煮詰まるし、上級者や他の人の視点で、視界や開ける。

rails チュートリアル4章まで終了
頑張ろう

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

Rubyのインクリメント速度のバージョンごとの比較

TL;DR

Rubyでは i++ ができない。
たまたま i++ しようとしたらエラーになったのでそういえばインクリメント演算子がRubyだと出来なかった気がするなと思い、念のために調べてみた。

公式ドキュメントじゃないけど、割と自明な話しだと思うので公式ではなく個人のテックブログを参照した。詳細に調べられていたので問題ないかなと思う。
そして恐らくだが以前も同じブログを参照した気がする。

Ruby にインクリメント演算子のようなものが無い理由

選択肢としてはi += 1i.succi.nextの3パターンあるがなにが違うのか気になったので調べたところ先人がすでに調べていた。

RubyのIntegerにおけるsuccとnext

ただこの記事も前述の記事も2014年〜2015年なので現在のRubyバージョンでは状況が変わっている可能性があるし、なんなら自分が問題を起こしたバージョン(ruby@2.4.1p111)とで結果が異なる可能性があるのではないか?と思い、せっかくなので調べてみた。

結論:

3つの中でsuccが比較的安定、速い。
次点でi += 1nextはFixnumで他に比べて遅い。

Rubyでインクリメントしたいとき、普段は i += 1i.succを使う。
パフォーマンスが気になる箇所では i.succ を使うのがベター。次点でi += 1i.nextを使ってたらレビューで指摘してあげたほうがいいかもしれない……くらいかな。

2つ目のブログで書かれている状態とほぼ同じ結果になったのでおそらくは原因も同じと推測される。
各バージョンのコードまでは検証するつもりがなかったので省略してるけど多分同じなんだろうと思う。

検証環境:

MacOS version 10.14.5(18F132)
MacBook Pro (Retina, 15-inch, Late 2013)
プロセッサ 2.3 GHz Intel Core i7
memory 16 GB 1600 MHz DDR3

検証項目:

各バージョンの最新 + 問題を起こしたバージョンで検証。
本来は何度か実行してその平均値で出すんだろうけどメンドイので1回しかやってないので何かの検証目的でいる場合はサンプルデータ1くらいの扱いでお願いします。

検証コードは2つ目のブログの検証コードをそのまま流用。
理由としては計測する方法が変わると期待する結果も変わってしまう可能性があるため、できるだけ条件を同じにしたかったから。

実行したコード(ファイル名が違うだけ)

example.ruby
require 'benchmark'

Benchmark.bm do |x|
  cnt = 100000000
  fixnum = 1
  bignum = 1 << 64


  x.report("+=1 Fixnum") do
    a = fixnum
    cnt.times{ a += 1 }
  end

  # Fixnum#succ
  x.report("Fixnum succ") do
    a = fixnum
    cnt.times{ a = a.succ }
  end

  # Fixnum#next
  x.report("Fixnum next") do
    a = fixnum
    cnt.times{ a = a.next }
  end

  x.report("+=1 Bignum") do
    a = bignum
    cnt.times{ a += 1 }
  end

  # Bignum#succ
  x.report("Bignum succ") do
    a = bignum
    cnt.times{ a = a.succ }
  end

  # Bignum#next
  x.report("Bignum_next") do
    a = bignum
    cnt.times{ a = a.next }
  end
end

ruby@2.4.1p111

Fixnumのときのnextだけが遅い。ブログの現象と同じ。

$ ruby example.rb
        user     system      total        real
 +=1 Fixnum  4.720000   0.000000   4.720000 (  4.724997)
 Fixnum succ  4.720000   0.000000   4.720000 (  4.727951)
 Fixnum next  5.870000   0.010000   5.880000 (  5.879860)
 +=1 Bignum 10.570000   0.000000  10.570000 ( 10.582265)
 Bignum succ 10.340000   0.010000  10.350000 ( 10.354154)
 Bignum_next 10.110000   0.010000  10.120000 ( 10.121698)

ruby@2.6.3

Fixnumでnextだけが遅いという現象が発生してたのでこれはなにか別のアプリケーションが影響してるのか?と思ってもう一回実行したんだけど結果ほぼ変わらずだったのでどうやらnextがFixnumのときだけ遅いっぽい。

$ ruby example.rb
       user     system      total        real
+=1 Fixnum  5.052053   0.007104   5.059157 (  5.068701)
Fixnum succ  4.829195   0.004266   4.833461 (  4.840140)
Fixnum next  6.159282   0.002336   6.161618 (  6.165148)
+=1 Bignum 10.592028   0.004909  10.596937 ( 10.601052)
Bignum succ 10.377451   0.003914  10.381365 ( 10.384787)
Bignum_next 10.320459   0.003760  10.324219 ( 10.327414)

ruby@2.5.5

こちらもブログの現象と同じといえる。

$ ruby example.rb
       user     system      total        real
+=1 Fixnum  4.719072   0.001677   4.720749 (  4.722440)
Fixnum succ  4.651602   0.001740   4.653342 (  4.654881)
Fixnum next  5.748883   0.001787   5.750670 (  5.752244)
+=1 Bignum 10.454205   0.007352  10.461557 ( 10.471455)
Bignum succ 10.872434   0.028426  10.900860 ( 10.935718)
Bignum_next 10.624259   0.024823  10.649082 ( 10.674851)

ruby@2.4.6

ブログの(ry

ruby example.rb
       user     system      total        real
+=1 Fixnum  4.430000   0.000000   4.430000 (  4.437439)
Fixnum succ  4.570000   0.020000   4.590000 (  4.615233)
Fixnum next  5.950000   0.020000   5.970000 (  6.008219)
+=1 Bignum 10.600000   0.030000  10.630000 ( 10.660327)
Bignum succ 10.050000   0.020000  10.070000 ( 10.093179)
Bignum_next 10.370000   0.020000  10.390000 ( 10.410649)

ruby@2.3.7

たまたまなのかもしれないけど一番古いRubyのバージョンが一番遅いだろうとなんとなく考えていたが結果が真逆だったのが面白い。
昔のほうが特定の条件下では高速に動作するのか。
とはいえそのためにあえて2.3.7使うひとはいないと思うけど、どうしても速度がほしいなら別の言語選ぶだろうしな。

ruby example.rb                                                                                                  [05 30 00:37:43]
       user     system      total        real
+=1 Fixnum  4.170000   0.000000   4.170000 (  4.173693)
Fixnum succ  4.150000   0.000000   4.150000 (  4.154439)
Fixnum next  5.280000   0.000000   5.280000 (  5.282451)
+=1 Bignum  9.170000   0.000000   9.170000 (  9.176064)
Bignum succ  9.230000   0.010000   9.240000 (  9.233617)
Bignum_next  9.420000   0.000000   9.420000 (  9.428198)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyのdefine_methodについて

はじめに

以下のようなコードを見て、difine_methodの使われ方が良く分からなかったため、調べてみました。
特に、ブロックパラメーターがたくさん使われているため、define_methodの後にあるブロックパラメーター i が何を意味しているのか理解できませんでした。

【訂正】当初の記事ではブロック内の| |で囲まれた部分(下記のコード例ではwordやnum、iのこと)を「ブロック引数」と表現していましたが、正しくは「ブロックパラメーター」でした。本文内の記述を訂正させて頂きました。
コメントを頂いた@scivolaさん、ありがとうございました!

合わせて、初学者の方の参考になればと思い、コード内で使われている記法やメソッドについて初心者目線で解説してみました。コードを読んでも何をしているか分からない方の参考になればと思います。

sample.rb
NUMBERS = %w(zero one two three four five six seven eight nine)

NUMBERS.each_with_index do |word, num|
  define_method word do |i = nil|
    i ? num * i : num
  end
end

p two      #=> 2
p two(2)   #=> 4
p nine     #=> 9
p nine(3)  #=> 27

%wについて

リファレンス
https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2fliteral.html#percent

Rubyには%記法というものがあり、文字列、配列やハッシュ等々を%記号を使って記載することができます。

# 通常の配列の書き方
p ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
#=> ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]

# %記法を使った書き方
p %w(zero one two three four five six seven eight nine)
#=> ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]

どちらも同じ結果を得られますが、%記法を使用した方が簡潔に記載することが出来ます。
上記のコードでは%wを使用して、zeroからnineまでの配列を作成し定数NUMBERSに代入しています。

Enumerable#each_with_indexについて

リファレンス
https://docs.ruby-lang.org/ja/2.5.0/method/Enumerable/i/each_with_index.html

each_with_index -> Enumerator
each_with_index {|item, index| ... } -> self

each_with_indexメソッドは通常のeachメソッドのように繰り返しを行いながら、各要素にインデックス番号を付番することが出来ます。ブロックを渡す場合はブロックパラメーターの第一引数(item)に各要素が順番に入り、第二引数(index)にインデックス番号が0番から自動で代入されていきます。

NUMBERS = %w(zero one two three four five six seven eight nine)

NUMBERS.each_with_index do |word, num|
  # 省略
end
#  word   num
# "zero"   0
#  "one"   1
#      省略
# "nine"   9

上記のコードでは%w記法を使って定義した配列NUMBERSに対してeach_with_indexを使用しているため、ブロックパラメーターの第一引数(word)に配列NUMBERSの各要素("zero"から"nine"まで)が順番に代入され、それぞれの要素に対して第二引数(num)のインデックス番号が0から順番に代入されています。
従って、"zero"には0のインデックスが、"one"には1のインデックスが付番され、最後の"nine"まで繰り返し行われます。

Module#define_methodについて

リファレンス
https://docs.ruby-lang.org/ja/2.5.0/method/Module/i/define_method.html

define_method(name, method) -> Symbol
define_method(name) { ... } -> Symbol

define_methodは第一引数(name)に指定した名称をメソッド名とし、第二引数(method)にそのメソッドの処理内容を指定します。
また、第二引数にブロックを渡した場合は、ブロック内の処理が第一引数(name)で指定したメソッドの処理内容になります。このブロック内の処理については、第一引数で作成したメソッドを呼び出した際に初めて実行されます。

NUMBERS = %w(zero one two three four five six seven eight nine)

NUMBERS.each_with_index do |word, num|
  define_method word do |i = nil|
   # 省略
  end
end

上記のコードの場合は、define_methodの第一引数としてwordを指定しています。このwordが何かと言うと、前述したeach_with_indexの第一引数に指定されていたwordです。each_with_indexのwordには配列NUMBERSの各要素が順番に代入されていました。従って、define_methodの第一引数にwordが指定されていると言うことは、配列NUMBERSの各要素の名称をメソッド名として定義している事になります。つまり、"zero"と言う名称のメソッドから"nine"と言う名称のメソッドがここで順番に定義されています。
各メソッドの処理内容についてはブロックを渡しています。前述したように、ブロック内の処理は定義したメソッドが呼び出されるまで実行はされません。(例えばtwoメソッドを呼び出した時に初めてブロック内の処理が実行されます)。またブロックパラメーターとしてiを指定しており、そのデフォルト値をnilにしています。
最後にブロック内の処理内容を確認します。

条件演算子(三項演算子)について

リファレンス
https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2foperator.html#cond
Rubyには条件演算子と言うものがあります(他の言語にもあります)

文法:
式1 ? 式2 : 式3
式1の結果によって式2または式3を返します。
if 式1 then 式2 else 式3 end
とまったく同じです。

リファレンスに書いてある通りなのですが、if文をこのように書くことが出来ます。
?と:で式が分けられています。式1は条件式に該当するので、真(true)か偽(false)を返す式を記載します。
式1が真なら式2が実行されます。式1が偽なら式3が実行されます。

NUMBERS = %w(zero one two three four five six seven eight nine)

NUMBERS.each_with_index do |word, num|
  define_method word do |i = nil|
    i ? num * i : num
  end
end

上記のコードではdefine_methodのブロック内で条件演算子が使用されています。
i と (num * i) と num が?と:で区切られているのが分かると思います。
ここで、Rubyでは「nilとfalse以外は全て真とされる」という決まりがあります。
参考
https://docs.ruby-lang.org/ja/2.5.0/class/FalseClass.html

false は nil オブジェクトとともに偽を表し、 その他の全てのオブジェクトは真です。

上記の条件演算子をもう一度見てみると、式1にはブロックパラメーターのiしか記載されていませんが、iにfalseかnil以外の値が代入されていれば(真ならば)式2(num * 1)が実行され、iにfalseかnilが代入されていれば(偽ならば)式3(num)が実行されます。ここで、iにはデフォルト値としてnilが設定されているため(ブロックパラメーター内で i = nilとなっている部分)今回の条件演算子は、

(式1が真)iにfalse以外の値が入れば
(式2を実行)num(each_with_indexで付番したwordに対応するインデックス番号)にiを掛けた数値を返す。
(式1が偽)iに何も指定がなければ(デフォルト値のままなら)
(式3を実行)numをそのまま返す事になります。

まとめ

sample.rb
NUMBERS = %w(zero one two three four five six seven eight nine)

NUMBERS.each_with_index do |word, num|
  define_method word do |i = nil|
    i ? num * i : num
  end
end

p two      #=> 2
p two(2)   #=> 4
p nine     #=> 9
p nine(3)  #=> 27

最初のコードをもう一度見てみます。特に最後の4行。
twoを呼び出すと2が返ってきます。これは、define_method wordの部分で定義したtwoメソッドを引数なしで呼び出しています。引数の指定が無いため、ブロックパラメーターiにはデフォルト値のnilが代入されます。するとブロック内の条件演算子ではiは偽と判断されるので、twoメソッドに対応するインデックスの2がそのまま返ってきています。
two(2)を呼び出すと4が返ってきます。今回は引数2を指定してtwoメソッドを呼び出しています。この引数2はdefine_methodの部分でwordのブロックパラメーターであるiに代入されます。

twoというメソッドはwordの部分で定義されており、その処理内容は続くブロックの中に記載されます。考え方は通常のメソッドの引数と同じです。

# (注)ここではdefine_methodで作成されるtwoメソッドを、普通に(?)定義した場合を示しています。
# 便宜上num=2としています。ブロックによる記述も、通常のメソッドの定義方法とあまり変らないのか、
# と感じて頂ければと思います。
def two(i = nil)
  num = 2
  i ? num * i : num
end

nineについても全く同じ考え方になります。

ちなみにsample.rbに関しては、twoメソッドやnineメソッドの引数に数値以外を渡すとエラーになります(例えばtwo("2")とか)。理由については試して頂ければ分かると思います。

【訂正】
当初の記事では上記のコードを下記のように記載していましたが、下記のコードではエラーが発生します。ローカル変数やスコープに関する認識が不足していました。
コメントを頂いた@scivolaさんには重ねてお礼申し上げます!

# (注)誤ったコードです。
num = 2
def two(i = nil)
  i ? num * i : num
end

終わりに

だんだん何について書いているのか分からなくなり、また、説明が冗長で逆にわかりづらかったら申し訳ありま温泉。
個人的にはブロックパラメーター i にはdefine_methodで作成したメソッドを呼び出す際の引数が代入される。という部分が、なるほどポイントでした。
自分と同じような初学者の方に、一部分でも参考になる点があれば幸いです。
また、誤り・改良すべき点等あればご指摘いただければありがたいがーです。

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