20191203のRubyに関する記事は14件です。

B - ROT N

過去問やっていきます
AtCoder Beginner Contest 146
Aは流石にやらなくていいかな

問題

https://atcoder.jp/contests/abc146/tasks/abc146_b
スクリーンショット 2019-12-03 22.57.29.png

1回目

回答

N = gets.to_i
S = gets.chomp

alf = ['A','B','C','D','E','F','G',
  'H','I','J','K','L','M','N','O','P',
  'Q','R','S','T','U','V','W','X','Y','Z']

result = ''
for c in S.chars
  x = alf.index(c)
  x = ( x + N ) % 26
  result += alf[x]
end

puts result

結果

スクリーンショット 2019-12-03 22.59.15.png

2回目

回答

N = gets.to_i
S = gets.chomp

N.times do |i|
  S = S.tr('A-Z', 'B-ZA')
end
puts S

結果

スクリーンショット 2019-12-03 23.00.44.png

感想

流石にAからZ書かなくてもいい方法あるよな〜と思ったけど、書き方が分からなかった。。。
メモリ使用量9割くらい減ったな。。。

追記

コメントを頂いて、もう一回やりました!ありがとうございます!
あと alphabet の頭をとったつもりが alf となっていたのめっちゃ恥ずかしい
(alfabet はポーランド語らしい。へえ〜)

回答

N = gets.to_i
S = gets.chomp
a = [*"A".."Z"]
puts S.tr(a.to_s, a.rotate(N).to_s)

追記2

to_s では配列の文字を繋げられないとのご指摘を頂きました。
今回はたまたま上手くいきましたが、
本来の想定では以下のコードの挙動になる予定でしたので、
追記しておきます!

N = gets.to_i
S = gets.chomp
a = [*"A".."Z"]
puts S.tr(a.join, a.rotate(N).join)

結果

スクリーンショット 2019-12-04 13.02.55.png

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

railsアプリではクラスインスタンス変数の注意する

クラスインスタンス変数の注意

右辺の実行結果をキャッシュするために以下の手法が使われる事があると思います。
1回目のメソッド実行時に実行結果をインスタンス変数@fooでキャッシュしておいて、2回目のメソッド実行時にはインスタンス変数の中身を返しています。

Class Foo
  def
    @foo ||= bar
  end
end

インスタンスメソッドの場合、インスタンス変数はFooクラスのオブジェクト毎に保持されます。
railsアプリ内のコードでこれをクラスメソッド内で使用する場合は注意が必要です。

Class Foo
  self.def
    @foo ||= bar
  end
end

クラスメソッド内でインスタンス変数はClassクラスのオブジェクト毎に保持され、
Classクラスのオブジェクトはrailsアプリケーション起動時から同じものを使い続けます。
その為、DB問合せ結果をキャッシュしてしまったりすると、アプリケーション起動後DBに初めて問い合わせした値を保持し続けてしまう為、その後別のリクエストでDBを更新しても、反映されない問題が発生します。

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

rubyワンライナーでwebサーバーを立てるTips

mockサーバとかサッと作成したい時に使うTipsです。
rubyコマンドを使ってワンライナーでwebサーバーを起動します。

手順

1. レスポンスボディのファイルを用意します。

response.rb
"Hello"

2. response.rbファイルが配置されているパスでruby -run -e httpd ./response.rb -p 3333実行します。

$ ls -la
response.rb

$ ruby -run -e httpd ./response.rb -p 3333

-> これでwebサーバーが起動します。

3. 確認してみる

curlで確認してみます。
ブラウザでもlocalhost:3333にアクサスしたらresponse.rbの中身が返ってきます。

$ curl localhost:3333
"hello"
::1 - - [03/Dec/2019:20:18:36 JST] "GET / HTTP/1.1" 200 8
- -> /
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#Rails OR #Ruby + ActiveSupport の String#in_time_zone で 2月29日 30日 31日 が3月にずれるのだが

Rubyとか言語使用に関わる、深い事情ありの話かと思った。

Date.new を利用した方が良いかな。

require 'active_support/core_ext'

'2019-2-29'.in_time_zone('Tokyo').to_s
# => "2019-03-01 00:00:00 +0900"

'2019-2-30'.in_time_zone('Tokyo').to_s
# => "2019-03-02 00:00:00 +0900"

'2019-2-31'.in_time_zone('Tokyo').to_s
# => "2019-03-03 00:00:00 +0900"

'2019-2-32'.in_time_zone('Tokyo').to_s
# ArgumentError: argument out of range


Date.new(2019, 02, 28).in_time_zone('Tokyo')
# => Thu, 28 Feb 2019 00:00:00 JST +09:00

Date.new(2019, 02, 29)
# ArgumentError: invalid date

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2804

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

さくらVPSでCentOS7 11.RailsプロジェクトをGitで共同開発

はじめに

自由にテスト出来るLinuxのサーバーがほしくて、さくらVPSで構築してみました。
順次手順をアップしていく予定です。

前回インストールしたRuby On Railsを共同開発できるようにGitで管理したいと思います。

目次

  1. 申し込み
  2. CentOS7インストール
  3. SSH接続
  4. Apache・PHPインストール
  5. MariaDBインストール
  6. FTP接続
  7. sftp接続
  8. phpMyAdminインストール
  9. 環境のバックアップ
  10. Ruby On Railsインストール
  11. RailsプロジェクトをGitで共同開発

11.RailsプロジェクトをGitで共同開発

前回、Ruby On Railsインストール時に作成したテスト用プロジェクトHelloWorkdを共同開発できるようにGitで管理したいと思います。

共同開発用のマシンは、ローカルのWindows10のマシンにします。
構成は、こんな感じです。

共有リポジトリは、GitHub等を使う方法もありますが、今回はプロジェクトの有るさくらVPSサーバー内に作成します。

Gitでは、開発マシンのソースを直接本番ソースにアップするのではなく、一度共有リポジトリにアップしたものを本番ソースから取りに行く形になります。
本番ソースも開発と同様ローカルポジトリですので、開発側がアップした変更は手動で本番ソースに取り込む必要があります。
これだと不便ですので、共有リポジトリが変更を受け取った時自動的に本番ソースが取りに行くように設定します。

さくらVPSにGitインストール

インストール

$ sudo yum install git-all

インストール後の設定

ユーザ名とメールアドレスを設定します

$ git config --global user.name "sakura"
$ git config --global user.email sakura@kogueisya.com

gitの出力をカラーリング

$ git config --global color.ui auto

ディフォルトエディタの設定

$ git config --global core.editor vim

エイリアス設定(「checkout」を「co」に設定)

$ git config --global alias.co checkout

確認

$ git config user.name
sakura

$ git config -l
user.name=sakura
user.email=sakura@kougeisya.com
color.ui=auto
core.editor=vim
alias.co=checkout
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true

$ cd
$ cat .gitconfig
[user]
        name = sakura
        email = sakura@kougeisya.com
[color]
        ui = auto
[core]
        editor = vim
[alias]
        co = checkout

共有リポジトリ(ベアリポジトリ)作成

さくらVPSサーバーに共有リポジトリを作成します。

共有リポジトリ用ディレクトリ作成

/optの中に「プロジェクト名.git」というフォルダを作成します。

$ sudo mkdir -p /opt/helloworld.git/

-pは/optフォルダがなければ作成します。

所有者変更

フォルダの所有者を 「4.Apache・PHPインストール」で作成したWebコンテンツ操作用のグループ(webadmin)にします。

$sudo chown root:webadmin /opt/helloworld.git/
$sudo chmod 2775 /opt/helloworld.git/ -R

ベアリポジトリを作成

$ cd /opt/helloworld.git
$ git init --bare --share
Initialized empty shared Git repository in /opt/helloworld.git/

---bareは、ベアリポジトリを作成するオプション
--shareは、ベアリポジトリを複数のユーザによって共有可能にするオプション

本番ソース

前回「10.Ruby On Railsインストール」で作成したHelloWorldプロジェクトにローカルリポジトリを作成し、リモートリポジトリにプッシュします。

リポジトリのセットアップ

$ cd /var/www/app/HelloWorld
$ git init
Reinitialized existing Git repository in /var/www/app/HelloWorld/.git/

除外ファイル指定

railsコマンド実行時に作成される.gitignoreファイルにリポジトリから除外するファイルを指定するためのルールが記載されています。
このファイルに以下を追加します。

$ vi .gitignore
# Ignore other unneeded files.
doc/
*.swp
*~
.project
.DS_Store
.idea
.secret

ファイルをインデックスに追加(addコマンド)

プロジェクトのファイルをコミット待ちの変更が格納される「ステージングエリア」という一種の待機場所に追加

$ git add .

確認
ステージングエリアにあるファイルのリスト表示

$ git status

コミット

ローカルリポジトリへの変更反映

$ git commit -m "Initalize repository"

コミットメッセージの履歴参照

$ git log
commit d3f7327c74c7175b75ca7d78f4a1cd576f5b6d9a
Author: sakura <sakura@kougeisya.com>
Date:   Mon Dec 2 15:49:37 2019 +0900

    Initialize repository

リモートリポジトリにプッシュ

リモートリポジトリのアドレスに「origin」という名前を付けて記録
(「origin」にしたのは、pushやpullコマンドは実行時にリモートリポジトリ名を省略するとoriginという名前を使用する為)

$ git remote add origin /opt/helloworld.git

リポジトリをプッシュ

$ git push -u origin master
Counting objects: 6674, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6272/6272), done.
Writing objects: 100% (6674/6674), 29.86 MiB | 8.65 MiB/s, done.
Total 6674 (delta 909), reused 0 (delta 0)
To /opt/helloworld.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

-uオプションは、「git push -u origin master」とすると次回から「git push」だけでpushしてくれます。

本番ソース反映の自動化

本番ソースも開発と同様ローカルポジトリですので、開発側がアップした変更は手動で本番ソースに取り込む必要があります。
これだと不便ですので、共有リポジトリが変更を受け取った時自動的に本番ソースが取りに行くように自動化します。

自動化にはフックを使います。
フックは、Gitでコマンドを実行する直前もしくは実行後に特定のスクリプトを実行する為の仕組みです。

フックの種類(サーバーサイド)

フック 概要
pre-receive クライアントからpushを受け取った直後に起動。
更新内容の確認が可能。
post-receive push内容の適用終了後起動。
通知を行う場合に便利。
update pushによる更新対称ブランチごとに起動。

共有リポジトリの中にある「hooks」の中に、フック名のスクリプトファイルを作成することで実現できます。

今回は、開発のローカルリポジトリからプッシュされ、その内容が適用された後に本番のローカルリポジトリからプルするスクリプトを実行したいので、「/opt/helloworld.git/hokks/post-receive」というスクリプトファイルを作成します。
スクリプトの内容は、本番ソースのドキュメントルートに移動し、publlを実行します。

$ vi /opt/helloworld.git/hooks/post-receive
#!/bin/sh
cd /var/www/app/HelloWorld/
git --git-dir=.git pull

スクリプトファイルを他のユーザから実行できるようにパーミッションを設定します。

$ chmod 755 /opt/helloworld.git/hooks/post-receive

以上でサーバー側の設定は完了です。

開発用ローカルマシン(Windows10)の設定

各ツールのインストール

Ruby、Rails、Git等をインストールしていきます。

Rubyインストール

下記サイトからダウンロードします。
最新版の「Ruby+Devkit 2.6.5-1 (x64)」をダウンロードしました。
https://rubyinstaller.org/downloads/

ダウンロードしたファイルを実行すると下記の画面が表示されます。
「◎I accept the License」を選択し[Next>]をクリックします。

下記画面が表示されたら、チェックボックス3つともチェックをいれ、[Install]をクリックします。

下記画面が表示されたらそのまま[Next>]をクリックします。

下記画面のようにインストールが開始されますので、終わるまでしばらく待ちます。

インストールが終了すると、下記画面が表示されます。
そのまま[Finish]をクリックすると画面が閉じ、インストールが完了します。

引き続き下記の画面が開きます。
1,2,3と入力して[Enter]キーを押すとインストールが開始されます。

インストールが完了すると下記画面のように「Which components shall be installed? If unsure press ENTER []」と表示されますので、[ENTER]キーを押すと画面が閉じます。

Railsに必要なgemをインストール

SQLite3インストール

コマンドプロンプトを開き、以下を実行します。

> ridk exec pacman -S mingw-w64-x86_64-sqlite3

下記画面のように「インストールを行いますか? [Y/n]」と聞いてきますので「Y」を入力します。

プロンプトに戻ったらインストール終了です。
引き続き以下のコマンドを実行します。

> gem install sqlite3 --platform ruby

下記画面のようにプロンプトに戻ったら完了です。

nokogiriインストール

コマンドプロンプトで以下を実行します。

> ridk exec pacman -S mingw-w64-x86_64-libxslt

下記画面のように「インストールを行いますか? [Y/n]」と聞いてきますので「Y」を入力します。

プロンプトに戻ったらインストール終了です。
引き続き以下のコマンドを実行します。

> gem install nokogiri --platform ruby -- --use-system-libraries

下記画面のようにプロンプトに戻ったら完了です。

Node.jsインストール

下記サイトからダウンロードします。
https://nodejs.org/ja/download/
「LTS推奨版」「Windows Installer (.msi)」の「64-bit」をダウンロードしました。

ダウンロードしたファイルを実行すると下記の画面が表示されますので[Next]をクリックします。

下記画面が表示されたら「□I accept the terms in the License Agreement」にチェックを入れ[Next]をクリックします。

下記画面が表示されますので、そのまま[Next]をクリック。

下記画面もそのまま[Next]クリック。

下記画面が表示されたら「Automatically install the ・・・」にチェックをいれて[Next]をクリック。

下記画面の[Install]クリックで、インストールが開始されます。

インストールが終わるまでしばらく待ちます。

インストールが終わると下記画面が開きますので[Finish]をクリックすると画面が閉じます。

引き続き下記の画面が開きます。
「継続するには何かキーを押してください ...」と2回表示されますので都度[Enter]キーを押してください。

下記の画面が開き、インストールが開始されます。

下記のように「Tyoe ENTER to ext:」と表示されたら完了です。
[Enter]キーを押すと画面が閉じます。

Bundlerインストール

コマンプロンプトを開き下記のコマンドを実行します。

> gem install bundler

Gitインストール

GUIでGitが使えるTortoiseGitを使います。
画面のキャプチャは取っていませんでしたm(__)m

Git For Windows

下記サイトの[Download]ボタンよりダウンロード
ダウンロードしたファイルを実行し、画面に従いインストール
https://gitforwindows.org/

TortoiseGit

下記サイトのfro 64-bit Windowsの「Download TortoiseGit 2.9.0-64.bit(~19.5Mib)」をクリックしてダウンロード
ダウンロードしたファイルを実行し、画面に従いインストール
https://tortoisegit.org/download/

メニュー等を日本語に設定

下記サイトからLanguage PacksのJapanese 64Bitの[Setup]をクリックしてダウンロード
ダウンロードしたファイルを実行し、画面に従いインストール
https://tortoisegit.org/download/

インストール後、デスクトップの適当な場所で右クリックし表示されるメニューから[TortoisGit]-[設定]を選択

開いた画面の[全般][TotoiseGit][言語(Language)]より「日本語(日本)」を選択、[OK]ボタンをクリック

以上でWIndows10の設定は完了です。

クローンを作成

共有リポジトリからクローンを作成します。
作成場所は、C:\Prj\HelloWorldにします。

TortoiseGitを利用してクローン作製

デスクトップ等適当な場所で右クリックし、表示されたメニューから[Gitクローン (複製)...]を選択します。

下記の設定画面が開きます

URLには以下の値を設定します。
"ssh://" + ユーザー名 + "@" + さくらVPSサーバーのアドレス + ":" + SSHポート番号 + "共有リポジトリディレクトリ"

ディレクトリは、クローンを作成するフォルダ名を設定します。
ここでは、「C:\Prj\HelloWorld」にしました。
フォルダは無ければ作成されます。

Putty鍵のロードにチェックを入れ、[...]をクリックし、秘密鍵を選択します。
ここで選択する秘密鍵は、「 7.sftp接続」の設定時に作成した秘密鍵ファイルです。

[OK]をクリックすると、秘密鍵のパスフレーズを聞いてきますので、「 7.sftp接続」で設定したパスフレーズを入力し[OK]をクリックします。

クローン作製中

無事終了しました

フォルダ確認

動作確認

サーバーを起動してみる

コマンドプロンプトを起動し、ディレクトリの移動。

> cd \Prj\HelloWorld

とりあえずサーバーを立ち上げてみたら、railsコマンドがみつからないので、「bundle install」しろと出ました。

>bundle exec rails s
bundler: command not found: rails
Install missing gem executables with `bundle install`

「bundle install」を実行すると、派手にエラーが出ました(T0T)

>bundle install
Fetching gem metadata from https://rubygems.org/.
Retrying fetcher due to error (2/4): Bundler::PermissionError There was an error while trying to read from `C:/Users/sugi/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/mini_portile2`. It is likely that you need to grant read permissions for that path.
.
Retrying fetcher due to error (3/4): Bundler::PermissionError There was an error while trying to read from `C:/Users/sugi/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/thread_safe`. It is likely that you need to grant read permissions for that path.
.
Retrying fetcher due to error (4/4): Bundler::PermissionError There was an error while trying to read from `C:/Users/sugi/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/websocket-driver`. It is likely that you need to grant read permissions for that path.
.
There was an error while trying to read from
`C:/Users/sugi/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/globalid`.
It is likely that you need to grant read permissions for that path.

どうもファイルの読み取り権限に問題があるようです。
調べてもよくわからなかったので、ちと乱暴ですが管理者としてコマンドプロンプトを実行してみました。

再度「bundle install」したら、無事動きました。

サーバー起動に再チャレンジ。

>bundle exec rails server -b 0.0.0.0
=> Booting Puma
=> Rails 5.2.4 application starting in development
=> Run `rails server -h` for more startup options
  Please add the following to your Gemfile to avoid polling for changes:
    gem 'wdm', '>= 0.1.0' if Gem.win_platform?
  Please add the following to your Gemfile to avoid polling for changes:
    gem 'wdm', '>= 0.1.0' if Gem.win_platform?
*** SIGUSR2 not implemented, signal based restart unavailable!
*** SIGUSR1 not implemented, signal based restart unavailable!
*** SIGHUP not implemented, signal based logs reopening unavailable!
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.5-p114), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

ブラウザで確認。
動いているようです。

コントローラーを作成しプッシュしてみる

コントローラを作成

>bundle exec rails generate controller hello
  Please add the following to your Gemfile to avoid polling for changes:
    gem 'wdm', '>= 0.1.0' if Gem.win_platform?
  Please add the following to your Gemfile to avoid polling for changes:
    gem 'wdm', '>= 0.1.0' if Gem.win_platform?
      create  app/controllers/hello_controller.rb
      invoke  erb
      create    app/views/hello
      invoke  test_unit
      create    test/controllers/hello_controller_test.rb
      invoke  helper
      create    app/helpers/hello_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/hello.coffee
      invoke    scss
      create      app/assets/stylesheets/hello.scss

app/controllers/hello_controller.rbにアクションメソッドindexを追加

class HelloController < ApplicationController
  def index
    render plain: 'こんにちは、世界!'
  end
end

config/routes.rbにルーティング設定

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  get 'hello/index', to: 'hello#index'
end

ローカルサーバーを起動し動作確認

> bundle exec rails s

プッシュ

変更したファイルをさくらVPSサーバの共有リポジトリにプッシュします。
その前に、ローカルリポジトリに変更をコミットします。

HelloWorldフォルダの上で右クリックし、表示されたメニューより[Gitコミット(C)->"master"...]を選択。

下記のようにコミット画面が表示されますので、「メッセージ」を入力し、追加ファイルにチェックを入れ[コミット]ボタンをクリックします。

下記画面のようにコミットが実行され、成功と表示されるとコミットは成功です。

コミットが成功したら、共有リポジトリへプッシュします。
上記画面の[プッシュ]ボタンをクリックすると下記のプッシュ画面が開きます。
[OK]をクリックし、サーバーの共有リポジトリにプッシュします。

プッシュが実行されます。
無事成功しました。

さくらVPSサーバーでの動作確認

Git確認

共有リポジトリへのプッシュ確認

$ cd /opt/helloworld.git
$ git log
commit 941e28b8e44d6dfcbe2ba2bdf5189cba76b92b38
Author: Kouichi Sugimoto <sugi@kougeisya.com>
Date:   Tue Dec 3 18:06:20 2019 +0900

    Windowsからのプッシュテスト

commit d3f7327c74c7175b75ca7d78f4a1cd576f5b6d9a
Author: sugi <sugi@kougeisya.com>
Date:   Mon Dec 2 15:49:37 2019 +0900

    Initialize repository

プッシュはうまくいっているようです。
開発リポジトリから共有リポジトリにプッシュがあると、自動的に本番サーバーがプルするように設定していました。
これが動いているかログをチェックします。

$ cd /var/www/app/HelloWorld
$ git log
commit 941e28b8e44d6dfcbe2ba2bdf5189cba76b92b38
Author: Kouichi Sugimoto <sugi@kougeisya.com>
Date:   Tue Dec 3 18:06:20 2019 +0900

    Windowsからのプッシュテスト

commit d3f7327c74c7175b75ca7d78f4a1cd576f5b6d9a
Author: sugi <sugi@kougeisya.com>
Date:   Mon Dec 2 15:49:37 2019 +0900

    Initialize repository

hello_controller.rbが出来ているかチェック

$ cd /var/www/app/HelloWorld/app/controllers
$ ls
application_controller.rb  concerns  hello_controller.rb

動作確認

本番ソースのサーバーを起動します。

$cd /var/www/app/HelloWorld
$ rails server -b 0.0.0.0
=> Booting Puma
=> Rails 5.2.4 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.5-p114), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

ブラウザで確認

無事動きました。
これでGitを使った共同開発の環境が出来ました。

次回

次回は、Pythonのインストールの予定です。

前回:Ruby On Railsインストール

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

Macでrbenvが使えない!【binutilsの悪夢シリーズ】

以下のことがしたかった

  • rbenvでruby 2.6.5をインストール
  • 結論としては,binutilsが色々と悪さをする

環境

  • Mac Catalina 10.15.1

普通に入れようとした記録

$ brew install rbenv
... 略
$ rbenv install 2.6.5
Downloading openssl-1.1.1d.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2
Installing openssl-1.1.1d...

BUILD FAILED (OS X 10.15.1 using ruby-build 20191124)

Inspect or clean up the working tree at /var/folders/df/0q1vdcy17j32kzq9xlb5zgg40000gn/T/ruby-build.20191203170740.4037.sKb82m
Results logged to /var/folders/df/0q1vdcy17j32kzq9xlb5zgg40000gn/T/ruby-build.20191203170740.4037.log

Last 10 log lines:
      _s_server_main in s_server.o
  "_verify_stateless_cookie_callback", referenced from:
      _s_server_main in s_server.o
  "_wait_for_async", referenced from:
      _s_client_main in s_client.o
      _sv_body in s_server.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[1]: *** [apps/openssl] Error 1
make: *** [all] Error 2

はて,Rubyを入れる前のOpenSSLのビルドでコケているみたいだ...

解決策

ログが /var/folders/df/0q1vdcy17j32kzq9xlb5zgg40000gn/T/ruby-build.20191203170740.4037.log にあるようなので覗いてみる

${LDCMD:-clang} -arch x86_64 -O3 -Wall -L. -Wl,-search_paths_first -L/Users/hiragi/.rbenv/versions/2.6.5/lib \
                -o apps/openssl apps/asn1pars.o apps/ca.o apps/ciphers.o apps/cms.o apps/crl.o apps/crl2p7.o apps/dgst.o apps/dhparam.o apps/dsa.o apps/dsaparam.o apps/ec.o apps/ecparam.o apps/enc.o apps/engine.o apps/errstr.o apps/gendsa.o apps/genpkey.o apps/genrsa.o apps/nseq.o apps/ocsp.o apps/openssl.o apps/passwd.o apps/pkcs12.o apps/pkcs7.o apps/pkcs8.o apps/pkey.o apps/pkeyparam.o apps/pkeyutl.o apps/prime.o apps/rand.o apps/rehash.o apps/req.o apps/rsa.o apps/rsautl.o apps/s_client.o apps/s_server.o apps/s_time.o apps/sess_id.o apps/smime.o apps/speed.o apps/spkac.o apps/srp.o apps/storeutl.o apps/ts.o apps/verify.o apps/version.o apps/x509.o \
                 apps/libapps.a -lssl -lcrypto  
ld: warning: directory not found for option '-L/Users/hiragi/.rbenv/versions/2.6.5/lib'
ld: warning: ignoring file apps/libapps.a, building for macOS-x86_64 but attempting to link with file built for unknown-unsupported file format ( 0x21 0x3C 0x61 0x72 0x63 0x68 0x3E 0x0A 0x2F 0x20 0x20 0x20 0x20 0x20 0x20 0x20 )
Undefined symbols for architecture x86_64:

#{LDCMD:-clang} はたぶんldのこと,コンパイルはちゃんと通ってるけど,リンクするときにうまく行っていない.
で,apps/libapps.a とかいう多分shared-library的なもののフォーマットが合っていないといった旨のエラー.

それともうちょっと上側を見てみると,

ar: creating libcrypto.a
ranlib -c libcrypto.a || echo Never mind.
ranlib: invalid option -- c

invalid option -- cて...なんか別のコマンドが実行されている...?

ranlib error とかでググってみると,以下のページが出た

https://tetsuok.hatenablog.com/entry/2012/05/07/054356

なるほど,なんかOS標準のarranlibを使わないとなにか別のものができてしまう模様...
そして更に調べると

https://naoyat.hatenablog.jp/entry/2012/01/31/033312

こんな記事も見つけた,どうやらbrewのbinutilsを入れていると,そいつの依存でついてくるarranlibが勝手にパスを通してしまうらしい.... こんなのわかるわけ無いやろ(半ギレ)
そういえば以前もなにか自前でビルドしようとしたときになんとなくbinutilsを消していた気がする...恐るべしbinutils....

https://qiita.com/nagomiso/items/dc6021beb72d09f2128f
似たような記事がありました,ただ原因と問題が発生する場所が違いすぎて原因にたどり着きにくい...
本格的にLinuxを動かすことを検討しないと,こういう問題が起きる毎に無駄に時間を使ってしまう.

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

Excelって名前しか知らないPCに不慣れな感覚派22歳プログラミング完全未経験者がRubyを習得するまでの軌跡。Part4

【HTML-CSS編】

はじめはpart2,3のように用語の説明や文型をまとめようとおもったがprogateの説明だけで完成されており、理解することが可能。最低限大事なことだけまとめる。

1.HTMLとCSSってなんなのか CSSは色、大きさ、形などデザインに関するプログラミング HTMLが素材でCSSが職人。
2.CSSはHTMLとは別にファイルを作って記述する。
3.プロパティはCSSの機能 ex.color.font-size,background-color,width,height

やってて感じたこと

HTML-CSSはRubyと比べると内容自体はすごく簡単で意味がわからないということはほぼないと想う。ただRubyが詩だとするとHTML-CSSは単文といった感じで1文1文で完結していて面白みはあまりない。

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

Rails 6.0.x標準で Ajax+(jQuery+Partial) でHTML部分更新する世界一シンプルなサンプル

TL;DR (長い! 3行で!)

↓ の記事をRails 6.0.1 版に書き直したものです.
Rails 5.x標準で Ajax+(jQuery+Partial) でHTML部分更新する世界一シンプルなサンプル

Front EndがWebpackに変わっており,
・ Coffee Script → JavaScript への変更
・ jQuery導入方法の変更
が大きな変更点です.

動作確認環境

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.6 LTS ← 古いLTSなのでわりとダメ,できれば最新LTS使ってください
Release:        14.04
Codename:       trusty

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]

$ rails --version
Rails 6.0.1

$ yarn --version
1.16.0

$ nvm --version
0.35.1

$ node --version
v12.13.1

NOTICE

nodeのversionが古いと(LTSじゃないと?),$ rails newの途中で ↓ のようなErrorが出ます.

error get-caller-file@2.0.5: The engine "node" is incompatible with this module. 
Expected version "6.* || 8.* || >= 10.*". Got "9.10.1"
error Found incompatible module.

これ以降,webpackが使えなくなります.
(error Command "webpack" not found.)
$ rails new自体は "Webpacker successfully installed" のメッセージがでて正常終了してるように見えてしまうため気付けない...

ざっくりシーケンス

だいたい ↓ のようなシーケンスを実現します.
Client-Server間でSession張って双方向通信とかそういうところはRails Frameworkが全部やってくれます.

Front End : UserがInputしたTextをAjaxでBack End側にPost
↓
Back End : PostされたTextとPartialを使って部分的に差し替えるHTMLをRendering
↓
Front End : JavaScriptでAjaxのCallback(部分的に差し替えるHTML)を受け取る
↓
Front End : jQueryでHTMLの部分差し替え


Rails 6 でFront EndがWebpack化されたことで,Rails 5 に比べてReactなども導入しやすくなっています.そのため,Back Endから返すCallbackはpre-renderしたHTMLでなく,Jsonなどでデータを返して,Front End側でrenderする,という方法もあります.

Project初期化 + Default動作確認

$ rails new ajax-test

でRails Projectを初期化します.$ cd ajax-testでRailsのrootに移動して,

$ rails server

でサーバを起動して,
http://localhost:3000/
にアクセスして "Yay! You're on Rails!" のDefaultページが出たらOKです.

Static Page作成

Ajax動作のベースになるStatic Page部分を実装します.

Controller

$ rails generate controller AjaxTest

でControllerのTemplateを作成後,Action(Method)を追加します.

app/controller/ajax_test_controller.rb
class AjaxTestController < ApplicationController
  def top
    # NOP.
  end

  def update
    # TBD.
  end
end

top : Topページ表示用,特に処理はありません.
update : Ajaxリクエストを受ける用,実装はあとで追加します.

Route Config

ControllerのActionに繋げるためのRoute定義を追加します.

config/routes.rb
get  'ajax_test/top',    to: 'ajax_test#top',    as: 'ajax_test_top'
post 'ajax_test/update', to: 'ajax_test#update', as: 'ajax_test_update'

GET : Topページ表示用.
POST : Ajaxリクエスト用.

View

ajax/test/top にアクセスしたときに表示するViewを追加します.

app/views/ajax_test/top.html.erb
<div id="request_ajax_update" >
  <%= form_tag(ajax_test_update_path, method: :post, remote: true) { %>
    <%= text_field :data, :text %>
    <%= submit_tag 'Post AJAX' %>
  <% } %>
</div>

<hr>

<div id="updated_by_ajax" >
  DEFAULT
</div>

<hr>

大きく2つのブロックだけです.

<div id="request_ajax_update" >
Ajax RequestをPostするFormを持つブロック.
Userが入力したTextを params[:data][:text] に詰めて,
ajax_test_update_path ( = /ajax_test/update) にPOSTします.

<div id="updated_by_ajax" >
Ajax Callbackを受けてHTMLの部分更新をするブロック.

Static Page動作確認

この時点で,
http://localhost:3000/ajax_test/top
にアクセスすると,↓ のようなページが表示されると思います.
default.png

ただし,Ajaxの実装がまだ無いので,Post AJAX ボタンをClickしても見た目上は何も起こりませんが,$ rails server が動いているConsoleにText Fieldに入れた文字列がParametersとして通知されているのを確認できると思います.

Parameters: {"data"=>{"text"=>"Hello World !"}, "commit"=>"Post AJAX"}

Ajax実装

いままでに作ったStatic PageにAjax実装を組み込んでInteractiveな機能を追加します.

Partial View

部分的に更新するHTMLの部品を追加します.

app/views/ajax_test/_ajax_partial.html.erb
<div>
  Callback Msg = <%= results[:message] %>
</div>

このPartialで <div id="updated_by_ajax" > の中身を差し替えます.

Controller Action

さきほど作ったControllerの update の中身を実装します.

app/controller/ajax_test_controller.rb
class AjaxTestController < ApplicationController
  def top
    # NOP.
  end

  def update
    post_text = params[:data][:text]
    results = { :message => post_text }
    render partial: 'ajax_partial', locals: { :results => results }
  end
end

Controllerに渡ってきた Parameters の中に格納されているUserが入力したTextを使ってPartialをrenderしています.
Partialのファイル名 _ajax_partial.html.erbajax_partial で使えるあたりはRailsの規約に沿っています.

ここで,

res = render_to_string partial: 'ajax_partial', locals: { :results => results }
puts res

のようなCodeを書いておくとPartialをrenderしたときの実際のOutputが見れます.

<div>
  Callback Msg = Hello World !
</div>

JavaScript実装

Rails 6 からFront EndにWebpackが標準で使われることになり,Coffee ScriptがDefaultで使われなくなっています.

かわりに,
app/javascript/packs/application.js
のようなWebpackに対応したJavaScriptのDir構成に変わっています.
(Rails 5 でWebpack使っていた人にはおなじみの構成かも)

ViewにJavaScriptのEntry Pointを追加

ViewのHTMLにJavaScriptの読み込み部分を追加します.

app/views/ajax_test/top.html.erb
<div id="request_ajax_update" >
  <%= form_tag(ajax_test_update_path, method: :post, remote: true) { %>
    <%= text_field :data, :text %>
    <%= submit_tag 'Post AJAX' %>
  <% } %>
</div>

<hr>

<div id="updated_by_ajax" >
  DEFAULT
</div>

<hr>

<!-- ↓ 追加 -->
<%= javascript_pack_tag 'ajax_test' %>
<script>
  register_callback();
</script>

<%= javascript_pack_tag 'ajax_test' %>
app/javascript/packs/ 以下に置いてある ajax_test というファイル(の中身)を読み込む,という指示です.
実際にはWebpackがひとかたまりの.jsにしてしまうので,この名前のファイルを読み込んでいるわけではありません.

その後,<script></script> タグで register_callback() という関数を呼び出します.
この関数の実装は次で追加します.

JavaScript本体の実装

Webpack用Dir構成に沿って,Viewから呼び出せるように ↓ のJavaScriptを追加します.

app/javascript/packs/ajax_test.js
import * as $ from "jquery";

function register_callback() {

  $("#request_ajax_update").on(
      "ajax:complete",
      function(event) {
        var res = event.detail[0].response
        $('#updated_by_ajax').html(res)
      }
  );

}

window.register_callback = register_callback;

Viewから呼び出すregister_callback()関数の中で,AjaxリクエストをPOSTする #request_ajax_update Tagに ajax:complete のCallback関数を登録しています.

Viewから呼び出せるようにするため,
window.register_callback = register_callbcak;
の行でwindowの名前空間(global)にexportしています.
注) この方法は動作はしますがあまり行儀よくないはずです...

ただし,このままでTopページからPost AJAXをClickしても何も起こりません.
ChromeのDeveloper Tool等でConsole Logをみると,

Uncaught Error: Cannot find module 'jquery'
    at webpackMissingModule (ajax_test.js:1)
    at Module../app/javascript/packs/ajax_test.js (ajax_test.js:1)

のようなError Logがでていて,jquery の名前解決ができてないことがわかります.

jQueryを使えるようにする

Rails 5 から引き続きRails標準ではjQueryはサポートされていないため,WebpackからjQueryを使えるようにします.

$ yarn add jquery

のCommandでjqueryをInstallします.

必要かもしれない追加

"rails6 + jquery" とかのキーワードでぐぐると,↓ の2つの追加も必要,と出てきますが,手元の環境だと必要ありませんでした.
RailsのVersionによるのか,他の環境によるのか...

ref: https://www.botreetechnologies.com/blog/introducing-jquery-in-rails-6-using-webpacker

app/javascript/packs/application.js
// 他のrequireがいろいろ...

require("jquery") // 追加

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

// 追加
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)

module.exports = environment

Ajax動作確認

これで必要な変更はすべてです.
http://localhost:3000/ajax_test/top
にアクセスして,Text Fieldになにか文字列を入力してPost AJAXボタンを押したら,DEFAULT の文字列が更新されて ↓ のような画面になっていれば成功です.

ajax.png

ChromeのDeveloper ToolなどでDOM構造を見てみると,↓ のようになっていて,<div id="updated_by_ajax" >の中身が差し替わっているのが見えます.

dev_tool.png

おわり

Rails 6 でAjaxを使ったシーケンスを一本通すまでをできる限りDefaultのままで実現してみました.
環境依存でたまたまうまく動いている部分などあるかもしれません.もし何かおかしな点がありましたら教えていただけると助かります.

---///

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

CC0ライセンスの空白PDFファイル

はじめに

ライセンス的に問題のない「空白PDFファイル」が欲しかったのですが、すぐに見つからなかったので作りました。リポジトリはこちらです。

https://github.com/kaityo256/blank_pdf

空白PDFが欲しい

あるPDFファイルと別のPDFをpdftkで結合することを考えます。この時、二つ目のPDFを、いわゆる「奇数ページ起こし」にしたいことがあります。つまり、1つ目のファイル(A)が奇数ページ(例えば3ページ)で終わっており、二つ目のファイル(B)が偶数ページ(例えば4ページ)ある時、そのまま結合して両面印刷すると

A1,A2 | A3,B1 | B2, B3| B4

みたいになって読みづらくなります。これを、空白を一枚挟んで

A1,A2 | A3,空白 | B1, B2| B3, B4

としたくなりますね。もし空白のPDFblank.pdfがあれば、pdftkを使って

pdftk A.pdf blank.pdf B.pdf cat output output.pdf

でおしまいです。というわけで、PDFblank.pdfが欲しいわけですが・・・、空白のPDFってどうやって作るんだ?

で、ググって見ると、Rubyを使う方法も出てくるんですが、オンラインツール使う方法とか、Adobe Acrobatを使う方法とかが多い感じです。英語でググっても、なんかツールを使えみたいなのが多くて、うーん、という感じ。

たまに「白紙PDFをどうぞ」的なサイトもあるのですが、ライセンスが不明なのと、ダウンロードしてみると、ファイルに余計な情報が入っていたりして微妙だったりします。

というわけで、こちらでも紹介されている、Ruby + Prawnで空白PDFを生成することにします1

Ruby + Prawnで空白PDF

といっても、コードはこれだけ。

blank_pdf.rb
require "prawn"

doc = Prawn::Document.new(page_size: "A4")
doc.render_file "blank.pdf"

できあがるPDFも

%PDF-1.3
%????
1 0 obj
<< /Creator <feff0050007200610077006e>
/Producer <feff0050007200610077006e>
>>
endobj
2 0 obj
<< /Type /Catalog
/Pages 3 0 R
>>
endobj
3 0 obj
<< /Type /Pages
/Count 1
/Kids [5 0 R]
>>
endobj
4 0 obj
<< /Length 4
>>
stream
q
Q

endstream
endobj
5 0 obj
<< /Type /Page
/Parent 3 0 R
/MediaBox [0 0 595.28 841.89]
/CropBox [0 0 595.28 841.89]
/BleedBox [0 0 595.28 841.89]
/TrimBox [0 0 595.28 841.89]
/ArtBox [0 0 595.28 841.89]
/Contents 4 0 R
/Resources << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
>>
>>
endobj
xref
0 6
0000000000 65535 f 
0000000015 00000 n 
0000000109 00000 n 
0000000158 00000 n 
0000000215 00000 n 
0000000268 00000 n 
trailer
<< /Size 6
/Root 2 0 R
/Info 1 0 R
>>
startxref
540
%%EOF

と最低限で、余計な情報が入っていなくていい感じです。ちなみに二行目の「????」は、このファイルがバイナリであることを知らせるための非ASCIIデータです。一応このファイルをCC0ライセンスでリポジトリに置いておくので、必要な人はダウンロードして使ってください。

まとめ

なんか空白PDFが必要な人が、その都度スクリプトその他で作ってる気がしてアレな気がしますね。pdftkに白紙PDFを生成する機能があると一番幸せな気がしますが、作ってプルリクするのがいいのかな。


  1. 最初、このページ見て「RubyとlibHaruを使っている」ところまで見て、自分でPrawnで書いてから、後で見返してみたら「追記」にPrawnを使う方法が書いてあることに気が付きました。 

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

よくあるデータ出力機能を二段階に分けて作ったら機能の汎用性がとても高くなったはなし

この記事は食べログ Advent Calendar 2019 7日目の記事です。
よくあるデータ出力機能をふたつに分けて作ったら、機能の汎用性がとても高くなったはなしをします。

おそらくよくあるデータ出力処理

ふだん、データ出力ってどのように作りますか?

自分は、こんな感じで
レコードの絞り込みとデータの取得を同時に行なって
1レコードずつ出力したい順に情報を並べ替えてファイルに出力
することが多いです。

おそらくよくあるデータ出力実装.rb
records = Employee.select(:age, :first_name, :last_name).where(job: :engineer)
CSV.open("engineer.csv") do |csv|
  csv << [:first_name, :last_name, :age] # headers
  records.each do |record|
    csv << [record.first_name, record.last_name ,record.age]
  end
end

いつものようにしたけれど、コードが長くなっちゃった

その日は出力したい情報の種類が多いデータ出力を作っていました。
いつものように作ってみたけれど、データの取得先だけで5種類もあってコードがとても長い、、、
なんとかしたい。
そのときふと
出力したいレコードの絞り込みと、出力したいデータの取得処理を
分けてみたらどうだろうと思いました。

レコードの絞り込みと、データの取得をわけてみた

そうして出来上がったのがこちらです。

欲しいレコードの検索条件と
知りたいデータの項目名を定義したファイルを渡します。

id name tel address station url reviews_url yoyaku

するとまず、レコード絞り込み機能が
レコードの特定条件で絞り込み検索をして出力対象レコードのidのみを出力します。

id name tel address station url reviews_url yoyaku
100
200
300

続いて、このファイルがデータ取得機能に渡されて

id name tel address station url reviews_url yoyaku
100 カフェ 03-0000-0000 恵比寿南 恵比寿駅 /100/ /100/dtlrvwlst なし
200 ラーメン屋 03-0000-0000 恵比寿南 恵比寿駅 /200/ /200/dtlrvwlst なし
300 イタ飯屋 03-0000-0000 恵比寿南 恵比寿駅 /300/ /300/dtlrvwlst あり

idをキーにデータを取ってきて、項目名列に対応した情報を付加してくれます。

表の項目名は、データ取得先ごと色分けしてみました。
データ取得先はDBだったりAPIだったりするので、単純に結合もできず
特にAPIはデータを取るときにページングや並列処理をしたりもするのでわりと手間がかかります。

データ取得機能の汎用性がとてもよかった

長いコードを整理するために機能を分割したのですが
思いがけず、データ取得機能の汎用性がとても高いことに気づきました。

たとえば
レコードを絞り込む条件は違うけど、出力したいデータが似ている機能を新しく作りたいときは、
レコードの絞り込み部分だけ作れば、あとはデータ取得機能に渡すだけ!

あるいは
既にレコードは特定できていて、詳細情報を知りたいとき
id列を埋めたファイルをデータ取得機能に渡すだけ!

渡すだけ!これはなかなか汎用性があるぞう。

単純なデータ出力ではあまり恩恵はなさそうですが
今回ご紹介したように、データ出力のために用意する種類が多くて手間がかかるケースで
それを利用したいシーンが複数あると、たくさんの恩恵を受けられそうです。

ドメイン間は疎結合のまま、情報満載なデータ出力ができそうな予感

今回はひとつのドメインのデータ取得機能だけ作りましたが、別のドメインにもデータ取得機能を作り
知りたいデータの項目定義ファイルに、いろんなドメインのデータ取得機能を旅させることで
情報満載なCSVファイルを作ることもできそうです。

感想

ふだん何気なくやっていたことを少し変えてみたら新しい発見につながったので
今後も何気ないことを改めて考えてみて、いろんな発見をしていきたいですね!

明日は @hiroteru_ さんによる「UIViewの角丸と影のおはなし」です。
お楽しみに!!

最後までご覧いただきありがとうございました!

おまけ

データ出力機能を作るときに役立ちそうなコード集です。

最近のExcelってUTF-8を理解できるんですって(ただしBOMつきに限る)

最近のExcelは、UTF-8でもBOMつきであれば文字化けせずにcsvファイルを開いてくれるようです。

BOMつきUTF-8のCSVを作る
File.write(filepath, "\xEF\xBB\xBF") # excelで直接UTF-8を開けるようにするためにBOMをつける
CSV.open(filepath, 'a') do |csv|     # 追加書き込みモードで開く

これで「ファイルをインポート」機能とお別れできます。

この方法で作成したCSVを読み込むときは、BOMついてるかもよ。と教えてあげる必要があります。

BOMつきUTF-8のCSVを読み込む
CSV.open(filepath, encoding: "BOM|UTF-8") do |csv|

データ取得機能で活躍した遅延ロード

不必要なデータまで取得していると、処理に無駄な時間がかかってしまいます。
1つの取得先のデータだけあれば済むときに、5つの取得先からデータを取得したくないですよね。
こんなときに遅延ロードが大活躍しました。

class Hoge
  def initialize(id)
    @id = id
  end

  def name
    data.name
  end

  def tel
    data.tel
  end

  private

  def data
    return @data if defined? @data # メモ化
    @data = HogeData.select(:name, :tel).where(id: @id).take
  end
end

header = csv.headers # 出力したいと指定されている項目名を取得
-> ["name", "tel"]

hoge = Hoge.new(id)
header.each do |column|
  hoge.public_send(column.to_sym) # 初回にnameが呼び出されたときに、中のdataメソッドがデータを取得してくれる
  # 続いて tel が呼び出されたときは、メモ化された@dataを見るので改めてデータ取得はしない
end

このように、nameの情報を出力したい。と呼び出されたときにはじめてデータを取得することで不必要なデータは取得せずに済むようになりました。

あるクラスのメソッドをまとめてdelegateに定義する

delegateって明示的にメソッド名を定義する必要があって、面倒ですよね。
Hogeクラスのメソッド全部をdelegateしたいときは、こんな書き方で定義できました。

delegate *Hoge.instance_methods(false), to: :hoge

データ取得機能では、データの取得先毎にクラスをわけてオブジェクトを作っています。
メソッドが呼び出されたら、そのデータを持っているオブジェクトにdelegateするように、この方法で実現しています。

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

サンプルコードでわかる!Ruby 2.7の主な新機能と変更点 Part 1 - 番号指定パラメータ(numbered parameter)

はじめに

Rubyは毎年12月25日にアップデートされます。
Ruby 2.7については2019年11月23日にpreview3がリリースされました。

Ruby 2.7.0-preview3 リリース

この記事ではRuby 2.7で導入される変更点や新機能について、サンプルコード付きでできるだけわかりやすく紹介していきます。

ただし、Ruby 2.7は多くの新機能や変更点があり、1つの記事に収まらないのでいくつかの記事に分けて書いていきます。
本記事で紹介するのは番号指定パラメータ(numbered parameter)です。

本記事の情報源

本記事は以下のような情報源をベースにして、記事を執筆しています。

動作確認したRubyのバージョン

本記事は以下の環境で実行した結果を記載しています。

$ ruby -v
ruby 2.7.0preview3 (2019-11-23 master b563439274) [x86_64-darwin19]

フィードバックお待ちしています

本文の説明内容に間違いや不十分な点があった場合は、コメント欄や編集リクエスト等で指摘 or 修正をお願いします🙏

それでは以下が本編です!

ブロックの仮引数として番号指定パラメータが試験的に導入された

Ruby 2.7ではブロックの仮引数として番号指定パラメータ(numbered parameter)が試験的に導入されました。
これにより、|s| のように明示的に引数名を指定する代わりに、_1 のような連番でブロックの仮引数を受け取ることができます。

# 番号指定パラメータを使わない場合
%w(1 20 300).map { |s| s.rjust(3, '0') }
#=> ["001", "020", "300"]

# 番号指定パラメータを使う場合
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]
# 番号指定パラメータを使わない場合
[1, 2, 3, 4].inject(0) { |memo, n| memo + n }
#=> 10

# 番号指定パラメータを使う場合
[1, 2, 3, 4].inject(0) { _1 + _2 }
#=> 10

(Ruby初心者さん向けの補足)
上のコードに登場する%w()という構文は、文字列の配列を作成するための構文です。

%w(1 20 300)
# ↑のコードは、↓と書いているのと同じ
["1", "20", "300"]

割り当てられていない連番はnil

割り当てられない連番を指定するとnilが返ります。

# 9番目の仮引数は何も割り当てられないのでnil
%w(1 20 300).map { _9 }
#=> [nil, nil, nil]

使用できる連番は_1から_9まで

使用できる連番は_1から_9までです。
_10のような連番を指定するとNameErrorが発生します。

%w(1 20 300).map { _10 }
#=> NameError (undefined local variable or method `_10' for main:Object)

_1〜_9のようなローカル変数を宣言していると警告が出る

番号指定パラメータを使っているかどうかにかかわらず、_1_9のような変数名を宣言していると、コードを読み込んだタイミングで警告が出ます。

# 番号指定パラメータと同名の変数を宣言すると警告が出る
_1 = "999"
#=> warning: `_1' is used as numbered parameter

ですので、既存のコードをRuby 2.7環境で動かすと、場合によってはこのような警告が出る可能性があります。

同名のローカル変数があるとブロック内でも変数が優先される

ブロックの外で同名の変数が宣言されている場合、ブロック内では番号指定パラメータではなく、ブロックの外で宣言されたローカル変数として参照されます。

# 番号指定パラメータと同名の変数を宣言しておく
_1 = "999"

# _1はローカル変数の"999"として扱われる
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["999", "999", "999"]

ただし、同名のローカル変数がブロックの後ろで宣言されていた場合はこの限りではありません。

# _1が番号指定パラメータとして機能する
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]

# 同名のローカル変数をブロックの後ろで宣言する
_1 = "999"

同名のメソッドがあると番号指定パラメータが優先される

_1のような名前のメソッドが定義されている場合は、番号指定パラメータが優先されます。

# 番号指定パラメータと同名のメソッドを定義しておく
def _1
  '123'
end

# _1は番号指定パラメータとして扱われる
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]

# _1() または self._1 とすればメソッドを明示的に呼び出せる
%w(1 20 300).map { _1().rjust(3, '0') }
#=> ["123", "123", "123"]

%w(1 20 300).map { self._1.rjust(3, '0') }
#=> ["123", "123", "123"]

ちなみに、Ruby 2.6だと番号指定パラメータの構文が導入されていないため、メソッドの_1が呼ばれます。

def _1
  '123'
end

# Ruby 2.6の場合はメソッドの_1が呼ばれる(Ruby 2.7と挙動が異なる)
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["123", "123", "123"]

Ruby 2.6と2.7では挙動が異なりますが、この場合は警告も出ないため、運が悪いと「Ruby 2.7に上げたら、なぜかちゃんと動かなくなった!」というトラブルが起きるかもしれません。(まあ、こんなコードを書いている人は滅多にないと思いますが・・・)

ネストしたブロックで番号指定パラメータを使う際の注意点

番号指定パラメータをネストしたブロックの中で使おうとするとsyntax errorが発生します。

sum = 0
[*1..4].each_slice(3) do
  # 外側のブロックで番号指定パラメータを使う
  _1.each do
    # 内側のブロックでも番号指定パラメータを使おうとするとエラーになる
    sum += _1
  end
end
#=> numbered parameter is already used in (SyntaxError)
#   outer block here
#           sum += _1
#   ^~~~~~~~~~~~~~~~~

外側のブロックだけ、もしくは内側のブロックだけで使うのはOKです。

sum = 0
[1, 2, 3, 4].each_slice(3) do |arr|
  arr.each do
    # 内側のブロックでだけ番号指定パラメータを使う
    sum += _1
  end
end
sum #=> 10

従来のブロック仮引数と混在させると構文エラーになる

次のコードのように、従来のブロック仮引数と混在させると構文エラーが発生します。

# 従来のブロック仮引数 |memo, n| と、番号指定パラメータ _1 が混在すると構文エラー
[1, 2, 3, 4].inject(0) { |memo, n| _1 + n }
#=> ordinary parameter is defined (SyntaxError)

ただし、明示的に番号指定パラメータと同名のブロック仮引数を指定した場合は構文エラーになりません。

# 番号指定パラメータと同名のブロック仮引数を使えば構文エラーにならない
[1, 2, 3, 4].inject(0) { |_1, n| _1 + n }
#=> 10

コラム:濫用厳禁!?番号指定パラメータの使いどころ

タイプ量が減るので一見便利に見える番号指定パラメータですが、個人的には「濫用厳禁、用量と用法を守って使いましょう」な新機能だなと感じます。

なぜなら連番だとコードを書いた人の意図がまったく見えないので、可読性が落ちるからです。
(単純に「連番は脳への負担が大きい」という問題もあります)

# 引数名を見れば「1, 2, 3, 4の値が渡されるのはnの方だな」と推測できる
[1, 2, 3, 4].inject(0) { |memo, n| memo + n }

# ん?どっちがどっちだ!?わからん!!
[1, 2, 3, 4].inject(0) { _1 + _2 }

irbなどでサクッと実行結果を確認したりするときに使うのは便利だと思いますが、実務で書く寿命の長いコードの場合はなるべく使用を控えた方が良い気がします。(そもそもRuby 2.7では「試験的な新機能」の扱いですので!)

まとめ

本記事ではRuby 2.7で試験的に導入された番号指定パラメータ(numbered parameter)を紹介しました。

冒頭でも説明したように「サンプルコードでわかる!Ruby 2.7の主な新機能と変更点」はいくつかの記事に分けて執筆します。
新しい記事を書いたらQiitaの「変更通知」でお知らせしますので、いちはやくキャッチしたい方は本記事のストックをよろしくお願いします!

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

Fullstaq Ruby を投入してみた(結果編に続く)

こんにちは。

以前見かけた Fullstaq Ruby を試してみようと思います。

Fullstaq Ruby

触れ込みについて

公式サイト の触れ込みでは

Ruby, optimized for production
A new Ruby distribution, with the Ruby you know and love, but better.

本番環境向けに調整されたより優れたRubyとのことです。

Less memory — save 30-50%

メモリ消費を3~5割抑え

Faster

高速で

More secure

セキュアで

Fully open source, based on MRI

MRIベースの完全なオープンソース

とのこと。

作者について

サイトをみていると、創設者は Hongli Lai と Fullstaq という会社のようです。
Hongli Lai はそこに記載があるように、 Passenger や Ruby Enterprise Edition の作者だそうですね。

最近の方は Ruby Enterprise Edition をご存じないかもしれません。

Ruby 1.8系の時代にメモリ使用率が激減することで、よく本番環境で用いられていたRuby処理系でした。

Rubyおじさんとしては「まじか、使えるんじゃないか」という気持ちにもなってきますね。

しくみ

週刊Railsウォッチで取り上げられていたのでそちらをお読みください。

週刊Railsウォッチ(20190821-2/2後編)11のgemにバックドア、ruby-jp Slackがとてもアツい、Fullstaq Rubyでチューンアップ、HTTPサービス監視chaoほか

導入

Server Edition 、Container Edition 、Heroku Edition と提供する予定のようですが、2019年12月3日現在は Server Edition のみのようです。

GET STARTED - Installation に RHEL/CentOS と Debian/Ubuntu のパッケージマネージャを使った方法が載っているので、そちらを読んで導入してみます。

今自由にできる環境がDocker環境しかなかったので、既存のDockerfileを手直しして入れてみました。
2019年12月3日現在、最新の Debian 10 buster 向けのパッケージは用意されていないようだったので、Debian 9 stretch をベースに使いました。

FROM buildpack-deps:stretch

ENV RUBY_VERSION 2.6.5

# Install dependencies
RUN apt-get update -qq \
  && apt-get install -y gnupg apt-transport-https ca-certificates curl

RUN echo 'deb https://apt.fullstaqruby.org debian-9 main' > /etc/apt/sources.list.d/fullstaq-ruby.list \
  && curl -SLfO https://raw.githubusercontent.com/fullstaq-labs/fullstaq-ruby-server-edition/master/fullstaq-ruby.asc \
  && apt-key add fullstaq-ruby.asc \
  && apt-get update -qq \
  && apt-get install -y fullstaq-ruby-${RUBY_VERSION} \
  && apt-get clean

ENV PATH $PATH:/usr/lib/fullstaq-ruby/versions/${RUBY_VERSION}/bin

# (省略)

結果

まだデータ不足。
1、2週様子を見て載せます。

動作には問題なく、テストはすべてパスし、エラートラッカーには特に情報は上がってきていません。

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

三井住友信託銀行プログラミングコンテスト2019に参加してみた

感想

AtCoderのコンテスト初参加。結構疲れた。てかムズイ。普通に数学的な発想が求められるんだな〜。
解ければ快感、解けなければうつ病。
https://atcoder.jp/contests/sumitrust2019
以下自分の回答

A November 30

M1,D1 = gets.chomp.split.map(&:to_i)
M2,D2 = gets.chomp.split.map(&:to_i)

ret = 0

if M1 == 12
  if D1 == 31
    ret = 1
  end
end

if M1 + 1 == M2 
  ret = 1
end

puts ret

D2が1かどうか判定すれば良かっただけなのに、なにしてるんだろうか。ぐぬぬ。

B Tax Rate

N = gets.to_i

X = ":("

a = N * 100 / 108
if ( N * 100 ) % 108 != 0
  a += 1
end

if a * 108 / 100 == N
  X = a
end
puts X

結構迷ってしまった。てか正規のやり方なのか怪しい。
Xに文字列いれた上に数値入れてグロい。Ruby凄いな。

C 100 to 105

X = gets.to_i

N = X / 100

can = 0

for i in 0..N
  x = X - i * 100
  if 0 <= x && x <= 5 * i
    can = 1
    break
  end
end

puts can

特にコメントなし(実際に解いてる時は一瞬で解けた嬉しさで内心ウッキウキだった)

D Lucky PIN

N = gets.to_i
S = gets.chomp

count = 0
for i in 0..9
  a = S.index(i.to_s)
  if a != nil
    for j in 0..9
      b = S.index(j.to_s, a+1)
      if b != nil
        for k in 0..9
          c = S.index(k.to_s, b+1)
          if c != nil
            count += 1
          end
        end
      end
    end
  end
end

puts count

最初30000個を3重ループして時間オーバーになった(あたりまえやん)
ここまでは100分以内にできた。

E Colorful Hats 2

ここから後日解き直した。

N = gets.to_i
A = gets.split.map &:to_i

t = [ 0, 0, 0 ]

result = 1
for i in 0..N-1
  if t.count( A[i] ) == 0
    result = 0
    break
  end
  result = ( result * t.count( A[i] ) ) % 1000000007
  for j in 0..2
    if t[j] == A[i]
      t[j] += 1
      break
    end
  end
end
puts result

これみんな簡単って言ってるのにわからなくて泣いた。
それぞれの帽子の数を保持して、その値と一致した分だけ場合分けができるという発想が無かった。
ブログラミングじゃなくても解けなかったと思う。悲しい。理系とは何だったのか。

F Interval Running

T = gets.split.map(&:to_i)
A = gets.split.map(&:to_i)
B = gets.split.map(&:to_i)

b = ( T[0] * A[0] + T[1] * A[1] ) - ( T[0] * B[0] + T[1] * B[1] )
if b == 0
  puts "infinity"
else
  a = T[0] * ( A[0] - B[0] )
  if a * b > 0
    puts 0
  else
    c = ( a / b ) * -2
    if a % b != 0
      c -= 1
    end
    puts c
  end
end

E問題で精神がやられて変数名とか見づらいしif文もおかしいけど一応解けた。
実際のコーディングだったら絶対に修正する。
A問題では変数に1つずつ入れているのにF問題では配列にいれる謎。(表記揺れってやつになるのかな)
自分で書いといてなんだけど*-2とかいう箇所気持ち悪すぎる。
横軸時間、縦軸距離のグラフを書くのがわかりやすかった。

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

ログイン中ユーザーの投稿内容をshowアクション以外で出力する方法

SNSっぽいアプリケーションのマイページで、ログイン中ユーザの投稿一覧を取得したかっただけなのにかなり詰まったので備忘録として残しておきます。

アプリの仕様上showアクションやshow.html.erbファイルを使用できず、index.html.erbで表示させるというレア?パターンではありますが。

環境

Rails 5.2.3
Ruby 2.5.1
gemのdeviseを使用
当記事で使用しているモデル名はplace

詰まった所

homes/index.html.erbファイル内にてeach doを利用し投稿内容を全部出力したい。

homes/index.html.erb
<% @myplaces.each do |myplace| %>
  #省略
<% end %>

全部のplaceを取得するのはhomesコントローラー内のindexアクションで

./controllers/homes_controller
@places = Place.all

こう書けばいい。そりゃそうだ。
問題はログイン中ユーザの投稿内容だけを取得したい時。

./controllers/homes_controller
@myplaces = Place.find(current_user.id)

と入力するとundefined method 'each' for nil:NilClassでエラーが発生する。これは@myplacesが空だからeachする元ネタがありませんよっていう怒られ方。できそうな気もするのに。

試しにターミナルでfind(1)として出力してみる。

ターミナル
pry(main)> Place.find(1)
   (0.5ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  Place Load (0.3ms)  SELECT  `places`.* FROM `places` WHERE `places`.`id` = 1 LIMIT 1
=> #<Place:0x00007f831ad85618
 id: 1,
 name: #以下省略

問題ない。
idが1であるcurrent_user.idを適当な変数に代入して出力してみる

./controllers/homes_controller
@user = current_user.id
homes/index.html.erb
<%= @user %>
#=> 1

こっちも問題がない。
なんで出力されないのか不思議で仕方がなかったんですが、驚いた事にこんな書き方だけで解決しました。

./controllers/homes_controller
@places = Place.all
places = @places
@myplaces = current_user.places
homes/index.html.erb
<% @myplaces.each do |myplace| %>
  #省略
<% end %>

目ン玉飛び出そうになりました。こんな簡単な記述でよかったんかと。。。普通にidを指定すれば出来るやろって余裕こいてたら痛い目見ました。

原因は今度時間を見つけて探してみます。今度作る時にはハマらないように…

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