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

rbenv で Rails 環境をローカルに作る

今どき Docker じゃなくて、Mac に Rails 環境を作る方法。

グローバルで使う Ruby の設定

$ rbenv global 2.6.1
$ rbenv rehash
$ ruby -v

Rails に依存する gem はすべて bundler 経由でインストールして、ローカルに保存する。グローバルの Ruby 環境は極力汚さない。

Ruby 2.6.1 のデフォルトの gem はこんなかんじ。

$ rbenv exec gem list
bigdecimal (default: 1.4.1)
bundler (default: 1.17.2)
cmath (default: 1.0.0)
csv (default: 3.0.4)
date (default: 2.0.0)
dbm (default: 1.0.0)
did_you_mean (1.3.0)
e2mmap (default: 0.1.0)
etc (default: 1.0.1)
fcntl (default: 1.0.0)
fiddle (default: 1.0.0)
fileutils (default: 1.1.0)
forwardable (default: 1.2.0)
io-console (default: 0.4.7)
ipaddr (default: 1.2.2)
irb (default: 1.0.0)
json (default: 2.1.0)
logger (default: 1.3.0)
matrix (default: 0.1.0)
minitest (5.11.3)
mutex_m (default: 0.1.0)
net-telnet (0.2.0)
openssl (default: 2.1.2)
ostruct (default: 0.1.0)
power_assert (1.1.3)
prime (default: 0.1.0)
psych (default: 3.1.0)
rake (12.3.2)
rdoc (default: 6.1.0)
rexml (default: 3.1.9)
rss (default: 0.2.7)
scanf (default: 1.0.0)
sdbm (default: 1.0.0)
shell (default: 0.7)
stringio (default: 0.0.2)
strscan (default: 1.0.0)
sync (default: 0.5.0)
test-unit (3.2.9)
thwait (default: 0.1.0)
tracer (default: 0.1.0)
webrick (default: 1.4.2)
xmlrpc (0.3.0)
zlib (default: 1.0.0)

パッケージマネージャの bundler はグローバルにインストールする

$ rbenv exec gem install bundler
$ rbenv rehash

Rails をローカルインストール

$ mkdir create-rails-project
$ cd create-rails-project

Gemfile を作る

$ cat << EOS > Gemfile
source "http://rubygems.org"
gem "rails", "5.2.2"
EOS

Rails を vender/bundle にインストールする

$ bundle install --path vendor/bundle

これで『ガワ』を作ったことになるので、このディレクトリ内で Rails プロジェクトを作る。

$ bundle exec rails new my-app --skip-bundle

--skip-bundle を必ず忘れないように。これを忘れると、グローバルの Ruby 環境に依存 gem がインストールされてしまう。

また、bundle exec rails new my-app -BCMT --skip-coffee -d sqlite3 とすると、 skip-bundle, skip-action-cable, skip-action-mailer, skip-test と CoffeeScriptのインストールがキャンセルされるので便利。-dは使用するデータベース種別を指定する。

こういう構成になった。

.
├── Gemfile
├── Gemfile.lock
├── my-app
└── vendor

my-app が実際に開発する Rails 環境になる。

$ mv my-app ../
$ cd ..
$ cd my-app

gem をローカルにインストールする。

$ bundle install --path vendor/bundle

vendor/bundle を git の管理外にする。

$ echo '/vendor/bundle' >> .gitignore

bundle lockコマンドの実行

$ bundle lock --add-platform x64-mingw32 x86-mingw32

このコマンドを実行しておくと、bundle install時の The dependency tzinfo-data (>= 0) will...のワーニングが表示されなくなる。

起動してみる。

$ bundle exec rails s

成功。

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

macOS Mojave で rails s が起動しない

エラーログ

Puma caught this error: Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile. (LoadError)

Gemfile の sqlite 箇所を以下のようにしたら治った

gem 'sqlite3', '~> 1.3.6'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

さくらのレンタルサーバでRubyを使う

さくらのレンタルサーバでRubyを使いたい。

$ ruby -v
ruby 1.8.7 (2012-10-12 patchlevel 371) [amd64-freebsd9]

えぇ…

rbenv使って自前で入れます。

$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ mkdir -p ~/.rbenv/plugins
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

これでrbenvとruby-buildの準備ができました。

$ rbenv install 2.6.1
ruby-build: TMPDIR=/tmp cannot hold executables (partition possibly mounted with `noexec`)

(バージョン番号は適宜読み替えてください)
/tmpに実行権限がないと怒られました。レンタルサーバーだししょうがないね。

$ mkdir tmp
$ TMPDIR=~/tmp rbenv install 2.6.1
Downloading ruby-2.6.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.1.tar.bz2
Installing ruby-2.6.1...

BUILD FAILED (FreeBSD 9.1-RELEASE-p24 using ruby-build 20190130)

Inspect or clean up the working tree at /home/example/tmp/ruby-build.00000000000000.00000
Results logged to /home/example/tmp/ruby-build.00000000000000.00000.log

Last 10 log lines:
                                    xmlrpc 0.3.0
installing rdoc:                    /home/example/.rbenv/versions/2.6.1/share/ri/2.6.0/system
installing html-docs:               /home/example/.rbenv/versions/2.6.1/share/doc/ruby
installing capi-docs:               /home/example/.rbenv/versions/2.6.1/share/doc/ruby
The Ruby openssl extension was not compiled.
ERROR: Ruby install aborted due to missing extensions
Configure options used:
  --prefix=/home/example/.rbenv/versions/2.6.1
  LDFLAGS=-L/home/example/.rbenv/versions/2.6.1/lib
  CPPFLAGS=-I/home/example/.rbenv/versions/2.6.1/include

つらい。

OpenSSLを自前ビルドします。(バージョン番号は適宜読み替えてください)

$ mkdir -p openssl/src
$ cd openssl/src
$ wget https://www.openssl.org/source/openssl-1.1.1a.tar.gz
$ tar -zxvf openssl-1.1.1a.tar.gz
$ cd openssl-1.1.1a
$ ./config --prefix=~/openssl/openssl
$ make
$ make test
$ make install

いざ。

$ TMPDIR=~/tmp RUBY_CONFIGURE_OPTS=--with-openssl-dir=~/openssl/openssl rbenv install 2.6.1
Downloading ruby-2.6.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.1.tar.bz2
Installing ruby-2.6.1...
Installed ruby-2.6.1 to /home/example/.rbenv/versions/2.6.1

長く苦しい戦いだった…

$ rbenv shell 2.6.1
$ ruby -v
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-freebsd9.1]

ちなみにRuby2.4までならOpenSSL自前で用意しなくてもビルドが通るんだけど、gem installが落ちる。

$ rbenv shell 2.4.5
$ ruby -v
ruby 2.4.5p335 (2018-10-18 revision 65137) [x86_64-freebsd9.1]
$ gem install bundle
ERROR:  Could not find a valid gem 'bundle' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: tlsv1 alert protocol version (https://api.rubygems.org/specs.4.8.gz)

なぜなのか

$ rbenv shell 2.4.5
$ ruby -ropenssl -e "p OpenSSL::OPENSSL_VERSION"
"OpenSSL 0.9.8zf 19 Mar 2015"

つらい

参考文献

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

Daru::Viewで都道府県のマップを描いてみた

都道府県別のデータをRESAS-APIから取得してDaru::Viewでグラフを描いてみました。本家のサンプルに国ごとのマップを描く方法は載っていますが、県別にマップに色つけする方法がなかったので記録を残します。

image.png

前提となる環境

  • Jupyter Notebook / Lab
  • IRuby

Daru::Viewとは?

Ruby用のデータフレーム Daru のライブラリ。バックエンドにGoogle Chartsや、Highchartsを利用する。Google Chartsの基本的な機能はほぼカバーされている。
https://github.com/SciRuby/daru-view

require 'daru/view'
require 'resas_kit'

RESAS-APIから情報の取得するのはResasKitというGemを利用した。(Qiitaに作者による解説記事があります)

client = ResasKit::Client.new(api_key: KEY) # キー取得には登録が必要

都道府県のリストを取得。あとでGoogle Chartに投げるために「県」「都」「府」を消しておきます。

prefs = client.get('prefectures').body.result.map do |item|
    name = item.prefName
    name.chop! unless name == "北海道"
    [name, item.prefCode]
end
#[["北海道", 1],
# ["青森", 2],
# ["岩手", 3],
# ...
# ["沖縄", 47]]

都道府県別の総人口を取得する

今回は練習のため、県ごとの総人口を取得してみます。

データの取得

県別のデータを一括して取得する方法がわからなかったので、ここではループをまわして取得しています。

population_data = prefs.map do |name, n|
    hoge = client.get 'population/composition/perYear', pref_code: n
    value = hoge.body.result.data[0].data[12].value # 12 は 2020年のデータ
    population_data << [name, value]
end

データフレームの作成

df = Daru::DataFrame.rows(population_data)
df.vectors = Daru::Index.new ["都道府県","総人口"]
df.head 3 # 上から3行を表示して確認

image.png

今回は使いませんが、nilの除去をする場合は下記のようにしたりします。

df.replace_values nil, 0 # nilを0に変換する

都道府県のチャートを描く

Daru::View::Plot.new(df,
    adapter: :googlecharts,
    region: 'JP',
    resolution: 'provinces',
    type: :geo,
    height: 500, width: 800
    ).show_in_iruby

image.png

おまけで、円グラフや棒グラフも描いておきましょう。

円グラフ

df2 = df.sort(["総人口"], ascending: false)
# ↑ ソートをかけた
Daru::View::Plot.new(df2,
    type: :pie,
    height: 500, width: 800
    ).show_in_iruby

image.png

棒グラフ

Daru::View::Plot.new(df,
    adapter: :googlecharts,
    type: :bar,
    vAxis: {textStyle: {fontSize: 8}},
    height: 800, width: 800
    ).show_in_iruby

image.png

 上記のコードを少し書き換えるだけでも、さまざまなグラフが作成できそうです。

 Pythonのすばらしい可視化ライブラリには遠く及びませんが、使用頻度の高い古典的なグラフについてはかなり便利に使えるのではないでしょうか。

 余談ですが、上記の簡単なコードを書くのには実はそれなりに時間がかかっています。初めて見たAPIで何の情報がどこに格納されているかを探すのに、かなり時間がかかるようです。

 この記事は以上です。

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

Rails で rubocop-rspec を使うときは、 rubocop-inflector も一緒に使うと便利

TL; DR

RSpec と Rails を一緒につかって開発するときは、 rubocop-inflector を gem install した上で、以下のような .rubocop.yml にすると、 Railsで利用している語形変化( ActiveSupport::Inflector の設定)がそのまま rubocopにも適用されます。

.rubocop.yml

require:
  - rubocop-rspec # If you are using rubocop-rspec, this should come first.
  - rubocop-inflector
  - ./config/initializers/inflections # Your custom rule file

config/initializers/inflections.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'RuboCop'
  inflect.acronym 'PvP'
end

RuboCop とは?

Rubocop は、Rubyのコードを静的解析してくれて、フォーマットを整えてくれる君です。
とても便利なツールなので、常日頃お世話になっている方が多いのではないでしょうか?

https://github.com/rubocop-hq/rubocop

RuboCop is a Ruby static code analyzer and code formatter. Out of the box it will enforce many of the guidelines outlined in the community Ruby Style Guide.

そして、 rubocop-rspec とは、RSpecに対しても、フォーマットを整えてくれる君です。

https://github.com/rubocop-hq/rubocop-rspec

チーム開発でRailsを利用する場合は、フォーマットに対して不毛な時間を費やさないためにも、これらは是非導入しておきたいところです。

rubocop-rspec を単体で使った場合の問題点

RuboCop は非常に良いツールです。そして Rubocop::RSpec もそのとおりです。
しかし、Railsを使って開発していると、かゆいところに手が届きづらいということがあります。

例: RSpec/FilePath について

このcopは、RSpecのファイルパスと、RSpec内でのテスト対象が一致しているかをチェックしてくれます。

https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FilePath

例えば、以下のようなテストファイルに対して、ファイル名が不適切だと指摘してくれます。

# my_class_spec.rb
describe MommyClass do # ファイル名も mommy_class_spec.rb にしろとおこられる
end

これは内部的には、対象を snake_case にした結果がファイル名と一致しているかを確認しています。
しかし、単純に snake_case にするのではなく、例えば、 RuboCoprubocop に変換するように、いくつかの言葉は一つの言葉として snake_case にしなければなりません。 rubocop-rspec を単体で利用する場合は、このような言葉は 設定値の CustomTransform に追加することで対応することが出来ます。

例えば、PvPという言葉を使っている場合は、以下のような設定値を書くことでこれを回避できます。

RSpec/FilePath:
  CustomTransform:
    PvP: pvp
    SyncPvP: sync_pvp
    AsyncPvP: async_pvp
    PvPOverPvP: pvp_over_pvp
    PvPController: pvp_controller

しかし、CustomTransformは完全一致であるため、利用しているパターンの数だけ書く必要があります。
そして、Railsを使っている方ならお気づきかもしれませんが、このような設定値ってどこかで見た記憶がありますよね?

rubocop-rspec も一緒に利用する場合

そうです。 config/initializers/inflections.rb に記述している ActiveSupport::Inflector の設定が、今回の問題を解消するための鍵になります。そして、この設定を簡単にrubocopに反映してくれるのが、以下の rubocop-inflector になります。

https://github.com/aeroastro/rubocop-inflector

これを導入することで、以下のような簡潔な設定を書くだけで、特殊な言葉への対応が完了します。そして、これらは Rails での inflector と同じ設定であるため、実際のコードと、Rubocopの設定が同期的にメンテナンスされるというメリットもあります。

require:
  - rubocop-rspec # If you are using rubocop-rspec, this should come first.
  - rubocop-inflector
  - ./config/initializers/inflections # Your custom rule file
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'PvP'
end

この rubocop-inflector はリリースされたばかりですが、非常に薄いgemであり、これを導入することで、Rubocopの設定ファイルのメンテナンスが非常に楽になるので、rubocop-rspec を Rails で利用されている方は、是非利用してみることをおすすめします。

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

anyenv 経由の rbenv をアップデートする

$ cd ~/.anyenv/envs/rbenv
$ git pull origin master
$ cd ./plugins/ruby-build
$ rbenv install -l

とすると、最新版の Ruby がインストールリストに表示されるのでインストールする。

anyenv 経由での *env を一括でアップデートする

こういうこともできるみたい。

$ anyenv update

こっちのほうが簡単でいいですね。anyenv 便利だわー。

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

Rubocop Airbnbの導入

はじめに

Rubocop Airbnbを導入したので、手順をメモします。

Rubocopとは

Rubocopは書かれたコードがRubyのコーディング規約に沿った書かれ方をしているかを自動的に確認してくれるgemです。

Rubocopではなく、Rubocop Airbnbを導入する理由

Rubocopはデフォルト設定だと自分で設定を変更する必要があるとのことなので、airbnbの開発で使用されているrubocopの設定をインストールできるRubocop Airbnbを導入することにしました。

Rubocop Airbnbの導入手順

基本的にRubocop Airbnbの手順に従いました。

Gemfileにgemを追加

gemを追加したら、bundle installします。

Gemfile
group :development do
  gem 'rubocop-airbnb'
end

Rubocop-airbnbを適用させるアプリファイルに、rubocop.ymlとrubocop-airbnb.ymlを作成し、それぞれ内容を記載

rubocop.ymlの内容は必要に応じて適宜変更してください。

rubocop.yml
 inherit_from:
  - .rubocop_airbnb.yml

 # Rails用に最適化
 Rails:
   Enabled: true

# 文字数の上限を80文字から変更
LineLength:
  Max: 130

 #rubocopで検証したくないフォルダを指定
 AllCops:
   Exclude:
     - 以下省略
rubocop-airbnb.yml
 require:
   - rubocop-airbnb

実行

設定が完了したら、bundle exec rubocop --require rubocop-airbnbで実行します。

以上で終了です。

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

各言語で定義できる関数のパラメータの最大個数

ちょっと気になって、関数で定義できるパラメータの最大個数について実験してみました。

255を閾値として、それ以上のパラメータを持つ関数を定義・実行できるのか、各言語のREPLを使って調べました。 (JavaScript はブラウザのコンソールです。)

JavaScript は256以上できたので "(unlimited)" としています、実際はもっと少ないかも。
C, C++, C# はある方からいただいたコメントを元にしています。
C, C++, NodeJS, PHP, Ruby について @Nabetani 様 よりいただいた情報を反映しています。

Language Version the maximum number note
C 127以上 コンパイラによって変わる。 オーバフローが発生するまで制限なく扱える場合もある。 SOLOLEARN, @Nabetani
C++ 256以上 コンパイラによって変わる。 オーバフローが発生するまで制限なく扱える場合もある。 SOLOLEARN, @Nabetani
C# 16383 オーバフローするまで成功すると思われる。 Stackoverflow
Python 3.6.5 255 256個以上のパラメータを持つ関数は定義不可。
PHP 7.2.9 262145 オーバフローするまで成功すると思われる。 @Nabetani
Ruby 2.5.1 130767 オーバフローするまで成功すると思われる。 @Nabetani
Kotlin 1.3.0 255 256個以上のパラメータを持つ関数は定義可能だが実行時に java.lang.NullPointerException が発生する。
Java 11.0.2 255 256個以上のパラメータを持つ関数は定義不可。
JavaScript Chrome 72.0.3626.96 (unlimited) オーバフローするまで成功すると思われる。
NodeJS 11.9.0 65535 ルール上は65535。 現実にはオーバフローするまで。 @Nabetani

特に Kotlin は 1.3 で 255個のパラメータを持つ関数を定義できるようになりました。 Java も、 ルールとして 255までパラメータ持てることになっています。 たぶん Scala も?

Python

シンタックスエラーが関数定義の時に255までしか受け付けませんとメッセージを返します。

>>> def f(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, a60, a61, a62, a63, a64, a65, a66, a67, a68, a69, a70, a71, a72, a73, a74, a75, a76, a77, a78, a79, a80, a81, a82, a83, a84, a85, a86, a87, a88, a89, a90, a91, a92, a93, a94, a95, a96, a97, a98, a99, a100, a101, a102, a103, a104, a105, a106, a107, a108, a109, a110, a111, a112, a113, a114, a115, a116, a117, a118, a119, a120, a121, a122, a123, a124, a125, a126, a127, a128, a129, a130, a131, a132, a133, a134, a135, a136, a137, a138, a139, a140, a141, a142, a143, a144, a145, a146, a147, a148, a149, a150, a151, a152, a153, a154, a155, a156, a157, a158, a159, a160, a161, a162, a163, a164, a165, a166, a167, a168, a169, a170, a171, a172, a173, a174, a175, a176, a177, a178, a179, a180, a181, a182, a183, a184, a185, a186, a187, a188, a189, a190, a191, a192, a193, a194, a195, a196, a197, a198, a199, a200, a201, a202, a203, a204, a205, a206, a207, a208, a209, a210, a211, a212, a213, a214, a215, a216, a217, a218, a219, a220, a221, a222, a223, a224, a225, a226, a227, a228, a229, a230, a231, a232, a233, a234, a235, a236, a237, a238, a239, a240, a241, a242, a243, a244, a245, a246, a247, a248, a249, a250, a251, a252, a253, a254, a255, a256, a257):
...     print(1)
... 
  File "<stdin>", line 1
SyntaxError: more than 255 arguments

PHP

256以上のパラメータでも定義・実行できます。

php > function f($a0, $a1, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9, $a10, $a11, $a12, $a13, $a14, $a15, $a16, $a17, $a18, $a19, $a20, $a21, $a22, $a23, $a24, $a25, $a26, $a27, $a28, $a29, $a30, $a31, $a32, $a33, $a34, $a35, $a36, $a37, $a38, $a39, $a40, $a41, $a42, $a43, $a44, $a45, $a46, $a47, $a48, $a49, $a50, $a51, $a52, $a53, $a54, $a55, $a56, $a57, $a58, $a59, $a60, $a61, $a62, $a63, $a64, $a65, $a66, $a67, $a68, $a69, $a70, $a71, $a72, $a73, $a74, $a75, $a76, $a77, $a78, $a79, $a80, $a81, $a82, $a83, $a84, $a85, $a86, $a87, $a88, $a89, $a90, $a91, $a92, $a93, $a94, $a95, $a96, $a97, $a98, $a99, $a100, $a101, $a102, $a103, $a104, $a105, $a106, $a107, $a108, $a109, $a110, $a111, $a112, $a113, $a114, $a115, $a116, $a117, $a118, $a119, $a120, $a121, $a122, $a123, $a124, $a125, $a126, $a127, $a128, $a129, $a130, $a131, $a132, $a133, $a134, $a135, $a136, $a137, $a138, $a139, $a140, $a141, $a142, $a143, $a144, $a145, $a146, $a147, $a148, $a149, $a150, $a151, $a152, $a153, $a154, $a155, $a156, $a157, $a158, $a159, $a160, $a161, $a162, $a163, $a164, $a165, $a166, $a167, $a168, $a169, $a170, $a171, $a172, $a173, $a174, $a175, $a176, $a177, $a178, $a179, $a180, $a181, $a182, $a183, $a184, $a185, $a186, $a187, $a188, $a189, $a190, $a191, $a192, $a193, $a194, $a195, $a196, $a197, $a198, $a199, $a200, $a201, $a202, $a203, $a204, $a205, $a206, $a207, $a208, $a209, $a210, $a211, $a212, $a213, $a214, $a215, $a216, $a217, $a218, $a219, $a220, $a221, $a222, $a223, $a224, $a225, $a226, $a227, $a228, $a229, $a230, $a231, $a232, $a233, $a234, $a235, $a236, $a237, $a238, $a239, $a240, $a241, $a242, $a243, $a244, $a245, $a246, $a247, $a248, $a249, $a250, $a251, $a252, $a253, $a254, $a255, $a256,  $a257) { echo 1; }
php >
php > function f($a0 = 1, $a1 = 1, $a2 = 1, $a3 = 1, $a4 = 1, $a5 = 1, $a6 = 1, $a7 = 1, $a8 = 1, $a9 = 1, $a10 = 1, $a11 = 1, $a12 = 1, $a13 = 1, $a14 = 1, $a15 = 1, $a16 = 1, $a17 = 1, $a18 = 1, $a19 = 1, $a20 = 1, $a21 = 1, $a22 = 1, $a23 = 1, $a24 = 1, $a25 = 1, $a26 = 1, $a27 = 1, $a28 = 1, $a29 = 1, $a30 = 1, $a31 = 1, $a32 = 1, $a33 = 1, $a34 = 1, $a35 = 1, $a36 = 1, $a37 = 1, $a38 = 1, $a39 = 1, $a40 = 1, $a41 = 1, $a42 = 1, $a43 = 1, $a44 = 1, $a45 = 1, $a46 = 1, $a47 = 1, $a48 = 1, $a49 = 1, $a50 = 1, $a51 = 1, $a52 = 1, $a53 = 1, $a54 = 1, $a55 = 1, $a56 = 1, $a57 = 1, $a58 = 1, $a59 = 1, $a60 = 1, $a61 = 1, $a62 = 1, $a63 = 1, $a64 = 1, $a65 = 1, $a66 = 1, $a67 = 1, $a68 = 1, $a69 = 1, $a70 = 1, $a71 = 1, $a72 = 1, $a73 = 1, $a74 = 1, $a75 = 1, $a76 = 1, $a77 = 1, $a78 = 1, $a79 = 1, $a80 = 1, $a81 = 1, $a82 = 1, $a83 = 1, $a84 = 1, $a85 = 1, $a86 = 1, $a87 = 1, $a88 = 1, $a89 = 1, $a90 = 1, $a91 = 1, $a92 = 1, $a93 = 1, $a94 = 1, $a95 = 1, $a96 = 1, $a97 = 1, $a98 = 1, $a99 = 1, $a100 = 1, $a101 = 1, $a102 = 1, $a103 = 1, $a104 = 1, $a105 = 1, $a106 = 1, $a107 = 1, $a108 = 1, $a109 = 1, $a110 = 1, $a111 = 1, $a112 = 1, $a113 = 1, $a114 = 1, $a115 = 1, $a116 = 1, $a117 = 1, $a118 = 1, $a119 = 1, $a120 = 1, $a121 = 1, $a122 = 1, $a123 = 1, $a124 = 1, $a125 = 1, $a126 = 1, $a127 = 1, $a128 = 1, $a129 = 1, $a130 = 1, $a131 = 1, $a132 = 1, $a133 = 1, $a134 = 1, $a135 = 1, $a136 = 1, $a137 = 1, $a138 = 1, $a139 = 1, $a140 = 1, $a141 = 1, $a142 = 1, $a143 = 1, $a144 = 1, $a145 = 1, $a146 = 1, $a147 = 1, $a148 = 1, $a149 = 1, $a150 = 1, $a151 = 1, $a152 = 1, $a153 = 1, $a154 = 1, $a155 = 1, $a156 = 1, $a157 = 1, $a158 = 1, $a159 = 1, $a160 = 1, $a161 = 1, $a162 = 1, $a163 = 1, $a164 = 1, $a165 = 1, $a166 = 1, $a167 = 1, $a168 = 1, $a169 = 1, $a170 = 1, $a171 = 1, $a172 = 1, $a173 = 1, $a174 = 1, $a175 = 1, $a176 = 1, $a177 = 1, $a178 = 1, $a179 = 1, $a180 = 1, $a181 = 1, $a182 = 1, $a183 = 1, $a184 = 1, $a185 = 1, $a186 = 1, $a187 = 1, $a188 = 1, $a189 = 1, $a190 = 1, $a191 = 1, $a192 = 1, $a193 = 1, $a194 = 1, $a195 = 1, $a196 = 1, $a197 = 1, $a198 = 1, $a199 = 1, $a200 = 1, $a201 = 1, $a202 = 1, $a203 = 1, $a204 = 1, $a205 = 1, $a206 = 1, $a207 = 1, $a208 = 1, $a209 = 1, $a210 = 1, $a211 = 1, $a212 = 1, $a213 = 1, $a214 = 1, $a215 = 1, $a216 = 1, $a217 = 1, $a218 = 1, $a219 = 1, $a220 = 1, $a221 = 1, $a222 = 1, $a223 = 1, $a224 = 1, $a225 = 1, $a226 = 1, $a227 = 1, $a228 = 1, $a229 = 1, $a230 = 1, $a231 = 1, $a232 = 1, $a233 = 1, $a234 = 1, $a235 = 1, $a236 = 1, $a237 = 1, $a238 = 1, $a239 = 1, $a240 = 1, $a241 = 1, $a242 = 1, $a243 = 1, $a244 = 1, $a245 = 1, $a246 = 1, $a247 = 1, $a248 = 1, $a249 = 1, $a250 = 1, $a251 = 1, $a252 = 1, $a253 = 1, $a254 = 1, $a255 = 1,  $a256 = 1) { echo 1; }
php > f()
php > ;
1

Ruby

256以上のパラメータでも実行できます。

2.5.1 :004 > def f(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, a60, a61, a62, a63, a64, a65, a66, a67, a68, a69, a70,
a71, a72, a73, a74, a75, a76, a77, a78, a79, a80, a81, a82, a83, a84, a85, a86, a87, a88, a89, a90, a91, a92, a93, a94, a95, a96, a97, a98, a99, a100, a101, a102, a103, a104, a105, a106, a107, a108, a109, a110, a111, a112, a113, a114, a115, a116, a117, a118, a119, a120, a121, a122, a123, a124, a125, a126, a127, a128, a129, a130, a131, a132, a133, a134, a135, a136, a137, a138, a139, a140, a141, a142, a143, a144, a145, a146, a147, a148, a149, a150, a151, a152, a153, a154, a155, a156, a157, a158, a159, a160, a161, a162, a163, a164, a165, a166, a167, a168, a169, a170, a171, a172, a173, a174, a175, a176, a177, a178, a179, a180, a181, a182, a183, a184, a185, a186, a187, a188, a189, a190, a191, a192, a193, a194, a195, a196, a197, a198, a199, a200, a201, a202, a203, a204, a205, a206, a207, a208, a209, a210, a211, a212, a213, a214, a215, a216, a217, a218, a219, a220, a221, a222, a223, a224, a225, a226, a227, a228, a229, a230, a231, a232, a233, a234, a235, a236, a237, a238, a239, a240, a241, a242, a243, a244, a245, a246, a247, a248, a249, a250, a251, a252, a253, a254, a255, a256, a257)
2.5.1 :005?>    puts 1
2.5.1 :006?>   end
 => :f
2.5.1 :001 > def f(a0 = 1, a1 = 1, a2 = 1, a3 = 1, a4 = 1, a5 = 1, a6 = 1, a7 = 1, a8 = 1, a9 = 1, a10 = 1, a11 = 1, a12 = 1, a13 = 1, a14 = 1, a15 = 1, a16 = 1, a17 = 1, a18 = 1, a19 = 1, a20 = 1, a21 = 1, a22 = 1, a23 = 1, a24 = 1, a25 = 1, a26 = 1, a27 = 1, a28 = 1, a29 = 1, a30 = 1, a31 = 1, a32 = 1, a33 = 1, a34 = 1, a35 = 1, a36 = 1, a37 = 1, a38 = 1, a39 = 1, a40 = 1, a41 = 1, a42 = 1, a43 = 1, a44 = 1, a45 = 1, a46 = 1, a47 = 1, a48 = 1, a49 = 1, a50 = 1, a51 = 1, a52 = 1, a53 = 1, a54 = 1, a55 = 1, a56 = 1, a57 = 1, a58 = 1, a59 = 1, a60 = 1, a61 = 1, a62 = 1, a63 = 1, a64 = 1, a65 = 1, a66 = 1, a67 = 1, a68 = 1, a69 = 1, a70 = 1, a71 = 1, a72 = 1, a73 = 1, a74 = 1, a75 = 1, a76 = 1, a77 = 1, a78 = 1, a79 = 1, a80 = 1, a81 = 1, a82 = 1, a83 = 1, a84 = 1, a85 = 1, a86 = 1, a87 = 1, a88 = 1, a89 = 1, a90 = 1, a91 = 1, a92 = 1, a93 = 1, a94 = 1, a95 = 1, a96 = 1, a97 = 1, a98 = 1, a99 = 1, a100 = 1, a101 = 1, a102 = 1, a103 = 1, a104 = 1, a105 = 1, a106 = 1, a107 = 1, a108 = 1, a109 = 1, a110 = 1, a111 = 1, a112 = 1, a113 = 1, a114 = 1, a115 = 1, a116 = 1, a117 = 1, a118 = 1, a119 = 1, a120 = 1, a121 = 1, a122 = 1, a123 = 1, a124 = 1, a125 = 1, a126 = 1, a127 = 1, a128 = 1, a129 = 1, a130 = 1, a131 = 1, a132 = 1, a133 = 1, a134 = 1, a135 = 1, a136 = 1, a137 = 1, a138 = 1, a139 = 1, a140 = 1, a141 = 1, a142 = 1, a143 = 1, a144 = 1, a145 = 1, a146 = 1, a147 = 1, a148 = 1, a149 = 1, a150 = 1, a151 = 1, a152 = 1, a153 = 1, a154 = 1, a155 = 1, a156 = 1, a157 = 1, a158 = 1, a159 = 1, a160 = 1, a161 = 1, a162 = 1, a163 = 1, a164 = 1, a165 = 1, a166 = 1, a167 = 1, a168 = 1, a169 = 1, a170 = 1, a171 = 1, a172 = 1, a173 = 1, a174 = 1, a175 = 1, a176 = 1, a177 = 1, a178 = 1, a179 = 1, a180 = 1, a181 = 1, a182 = 1, a183 = 1, a184 = 1, a185 = 1, a186 = 1, a187 = 1, a188 = 1, a189 = 1, a190 = 1, a191 = 1, a192 = 1, a193 = 1, a194 = 1, a195 = 1, a196 = 1, a197 = 1, a198 = 1, a199 = 1, a200 = 1, a201 = 1, a202 = 1, a203 = 1, a204 = 1, a205 = 1, a206 = 1, a207 = 1, a208 = 1, a209 = 1, a210 = 1, a211 = 1, a212 = 1, a213 = 1, a214 = 1, a215 = 1, a216 = 1, a217 = 1, a218 = 1, a219 = 1, a220 = 1, a221 = 1, a222 = 1, a223 = 1, a224 = 1, a225 = 1, a226 = 1, a227 = 1, a228 = 1, a229 = 1, a230 = 1, a231 = 1, a232 = 1, a233 = 1, a234 = 1, a235 = 1, a236 = 1, a237 = 1, a238 = 1, a239 = 1, a240 = 1, a241 = 1, a242 = 1, a243 = 1, a244 = 1, a245 = 1, a246 = 1, a247 = 1, a248 = 1, a249 = 1, a250 = 1, a251 = 1, a252 = 1, a253 = 1, a254 = 1, a255 = 1,  a256 = 1)
2.5.1 :002?>    puts 1
2.5.1 :003?>   end
 => :f 
2.5.1 :004 > f()
1
 => nil 

Kotlin

定義はできているように見えます。しかし実行時にエラーが出ました。

>>> fun f(a0: Int, a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int, a7: Int, a8: Int, a9: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int, a24: Int, a25: Int, a26: Int, a27: Int, a28: Int, a29: Int, a30: Int, a31: Int, a32: Int, a33: Int, a34: Int, a35: Int, a36: Int, a37: Int, a38: Int, a39: Int, a40: Int, a41: Int, a42: Int, a43: Int, a44: Int, a45: Int, a46: Int, a47: Int, a48: Int, a49: Int, a50: Int, a51: Int, a52: Int, a53: Int, a54: Int, a55: Int, a56: Int, a57: Int, a58: Int, a59: Int, a60: Int, a61: Int, a62: Int, a63: Int, a64: Int, a65: Int, a66: Int, a67: Int, a68: Int, a69: Int, a70: Int, a71: Int, a72: Int, a73: Int, a74: Int, a75: Int, a76: Int, a77: Int, a78: Int, a79: Int, a80: Int, a81: Int, a82: Int, a83: Int, a84: Int, a85: Int, a86: Int, a87: Int, a88: Int, a89: Int, a90: Int, a91: Int, a92: Int, a93: Int, a94: Int, a95: Int, a96: Int, a97: Int, a98: Int, a99: Int, a100: Int, a101: Int, a102: Int, a103: Int, a104: Int, a105: Int, a106: Int, a107: Int, a108: Int, a109: Int, a110: Int, a111: Int, a112: Int, a113: Int, a114: Int, a115: Int, a116: Int, a117: Int, a118: Int, a119: Int, a120: Int, a121: Int, a122: Int, a123: Int, a124: Int, a125: Int, a126: Int, a127: Int, a128: Int, a129: Int, a130: Int, a131: Int, a132: Int, a133: Int, a134: Int, a135: Int, a136: Int, a137: Int, a138: Int, a139: Int, a140: Int, a141: Int, a142: Int, a143: Int, a144: Int, a145: Int, a146: Int, a147: Int, a148: Int, a149: Int, a150: Int, a151: Int, a152: Int, a153: Int, a154: Int, a155: Int, a156: Int, a157: Int, a158: Int, a159: Int, a160: Int, a161: Int, a162: Int, a163: Int, a164: Int, a165: Int, a166: Int, a167: Int, a168: Int, a169: Int, a170: Int, a171: Int, a172: Int, a173: Int, a174: Int, a175: Int, a176: Int, a177: Int, a178: Int, a179: Int, a180: Int, a181: Int, a182: Int, a183: Int, a184: Int, a185: Int, a186: Int, a187: Int, a188: Int, a189: Int, a190: Int, a191: Int, a192: Int, a193: Int, a194: Int, a195: Int, a196: Int, a197: Int, a198: Int, a199: Int, a200: Int, a201: Int, a202: Int, a203: Int, a204: Int, a205: Int, a206: Int, a207: Int, a208: Int, a209: Int, a210: Int, a211: Int, a212: Int, a213: Int, a214: Int, a215: Int, a216: Int, a217: Int, a218: Int, a219: Int, a220: Int, a221: Int, a222: Int, a223: Int, a224: Int, a225: Int, a226: Int, a227: Int, a228: Int, a229: Int, a230: Int, a231: Int, a232: Int, a233: Int, a234: Int, a235: Int, a236: Int, a237: Int, a238: Int, a239: Int, a240: Int, a241: Int, a242: Int, a243: Int, a244: Int, a245: Int, a246: Int, a247: Int, a248: Int, a249: Int, a250: Int, a251: Int, a252: Int, a253: Int, a254: Int, a255: Int, a256: Int, a257: Int) { println(1) }
>>> fun f(a0: Int = 1, a1: Int = 1, a2: Int = 1, a3: Int = 1, a4: Int = 1, a5: Int = 1, a6: Int = 1, a7: Int = 1, a8: Int = 1, a9: Int = 1, a10: Int = 1, a11: Int = 1, a12: Int = 1, a13: Int = 1, a14: Int = 1, a15: Int = 1, a16: Int = 1, a17: Int = 1, a18: Int = 1, a19: Int = 1, a20: Int = 1, a21: Int = 1, a22: Int = 1, a23: Int = 1, a24: Int = 1, a25: Int = 1, a26: Int = 1, a27: Int = 1, a28: Int = 1, a29: Int = 1, a30: Int = 1, a31: Int = 1, a32: Int = 1, a33: Int = 1, a34: Int = 1, a35: Int = 1, a36: Int = 1, a37: Int = 1, a38: Int = 1, a39: Int = 1, a40: Int = 1, a41: Int = 1, a42: Int = 1, a43: Int = 1, a44: Int = 1, a45: Int = 1, a46: Int = 1, a47: Int = 1, a48: Int = 1, a49: Int = 1, a50: Int = 1, a51: Int = 1, a52: Int = 1, a53: Int = 1, a54: Int = 1, a55: Int = 1, a56: Int = 1, a57: Int = 1, a58: Int = 1, a59: Int = 1, a60: Int = 1, a61: Int = 1, a62: Int = 1, a63: Int = 1, a64: Int = 1, a65: Int = 1, a66: Int = 1, a67: Int = 1, a68: Int = 1, a69: Int = 1, a70: Int = 1, a71: Int = 1, a72: Int = 1, a73: Int = 1, a74: Int = 1, a75: Int = 1, a76: Int = 1, a77: Int = 1, a78: Int = 1, a79: Int = 1, a80: Int = 1, a81: Int = 1, a82: Int = 1, a83: Int = 1, a84: Int = 1, a85: Int = 1, a86: Int = 1, a87: Int = 1, a88: Int = 1, a89: Int = 1, a90: Int = 1, a91: Int = 1, a92: Int = 1, a93: Int = 1, a94: Int = 1, a95: Int = 1, a96: Int = 1, a97: Int = 1, a98: Int = 1, a99: Int = 1, a100: Int = 1, a101: Int = 1, a102: Int = 1, a103: Int = 1, a104: Int = 1, a105: Int = 1, a106: Int = 1, a107: Int = 1, a108: Int = 1, a109: Int = 1, a110: Int = 1, a111: Int = 1, a112: Int = 1, a113: Int = 1, a114: Int = 1, a115: Int = 1, a116: Int = 1, a117: Int = 1, a118: Int = 1, a119: Int = 1, a120: Int = 1, a121: Int = 1, a122: Int = 1, a123: Int = 1, a124: Int = 1, a125: Int = 1, a126: Int = 1, a127: Int = 1, a128: Int = 1, a129: Int = 1, a130: Int = 1, a131: Int = 1, a132: Int = 1, a133: Int = 1, a134: Int = 1, a135: Int = 1, a136: Int = 1, a137: Int = 1, a138: Int = 1, a139: Int = 1, a140: Int = 1, a141: Int = 1, a142: Int = 1, a143: Int = 1, a144: Int = 1, a145: Int = 1, a146: Int = 1, a147: Int = 1, a148: Int = 1, a149: Int = 1, a150: Int = 1, a151: Int = 1, a152: Int = 1, a153: Int = 1, a154: Int = 1, a155: Int = 1, a156: Int = 1, a157: Int = 1, a158: Int = 1, a159: Int = 1, a160: Int = 1, a161: Int = 1, a162: Int = 1, a163: Int = 1, a164: Int = 1, a165: Int = 1, a166: Int = 1, a167: Int = 1, a168: Int = 1, a169: Int = 1, a170: Int = 1, a171: Int = 1, a172: Int = 1, a173: Int = 1, a174: Int = 1, a175: Int = 1, a176: Int = 1, a177: Int = 1, a178: Int = 1, a179: Int = 1, a180: Int = 1, a181: Int = 1, a182: Int = 1, a183: Int = 1, a184: Int = 1, a185: Int = 1, a186: Int = 1, a187: Int = 1, a188: Int = 1, a189: Int = 1, a190: Int = 1, a191: Int = 1, a192: Int = 1, a193: Int = 1, a194: Int = 1, a195: Int = 1, a196: Int = 1, a197: Int = 1, a198: Int = 1, a199: Int = 1, a200: Int = 1, a201: Int = 1, a202: Int = 1, a203: Int = 1, a204: Int = 1, a205: Int = 1, a206: Int = 1, a207: Int = 1, a208: Int = 1, a209: Int = 1, a210: Int = 1, a211: Int = 1, a212: Int = 1, a213: Int = 1, a214: Int = 1, a215: Int = 1, a216: Int = 1, a217: Int = 1, a218: Int = 1, a219: Int = 1, a220: Int = 1, a221: Int = 1, a222: Int = 1, a223: Int = 1, a224: Int = 1, a225: Int = 1, a226: Int = 1, a227: Int = 1, a228: Int = 1, a229: Int = 1, a230: Int = 1, a231: Int = 1, a232: Int = 1, a233: Int = 1, a234: Int = 1, a235: Int = 1, a236: Int = 1, a237: Int = 1, a238: Int = 1, a239: Int = 1, a240: Int = 1, a241: Int = 1, a242: Int = 1, a243: Int = 1, a244: Int = 1, a245: Int = 1, a246: Int = 1, a247: Int = 1, a248: Int = 1, a249: Int = 1, a250: Int = 1, a251: Int = 1, a252: Int = 1, a253: Int = 1, a254: Int = 1, a255: Int = 1) { println(1) }
>>> f()
java.lang.NullPointerException
        at Line_1.f$default(Line_1.kts:1)
>>> f(1)
java.lang.NullPointerException
        at Line_1.f$default(Line_1.kts:1)
>>> fun f(a0: Int = 1, a1: Int = 1, a2: Int = 1, a3: Int = 1, a4: Int = 1, a5: Int = 1, a6: Int = 1, a7: Int = 1, a8: Int = 1, a9: Int = 1, a10: Int = 1, a11: Int = 1, a12: Int = 1, a13: Int = 1, a14: Int = 1, a15: Int = 1, a16: Int = 1, a17: Int = 1, a18: Int = 1, a19: Int = 1, a20: Int = 1, a21: Int = 1, a22: Int = 1, a23: Int = 1, a24: Int = 1, a25: Int = 1, a26: Int = 1, a27: Int = 1, a28: Int = 1, a29: Int = 1, a30: Int = 1, a31: Int = 1, a32: Int = 1, a33: Int = 1, a34: Int = 1, a35: Int = 1, a36: Int = 1, a37: Int = 1, a38: Int = 1, a39: Int = 1, a40: Int = 1, a41: Int = 1, a42: Int = 1, a43: Int = 1, a44: Int = 1, a45: Int = 1, a46: Int = 1, a47: Int = 1, a48: Int = 1, a49: Int = 1, a50: Int = 1, a51: Int = 1, a52: Int = 1, a53: Int = 1, a54: Int = 1, a55: Int = 1, a56: Int = 1, a57: Int = 1, a58: Int = 1, a59: Int = 1, a60: Int = 1, a61: Int = 1, a62: Int = 1, a63: Int = 1, a64: Int = 1, a65: Int = 1, a66: Int = 1, a67: Int = 1, a68: Int = 1, a69: Int = 1, a70: Int = 1, a71: Int = 1, a72: Int = 1, a73: Int = 1, a74: Int = 1, a75: Int = 1, a76: Int = 1, a77: Int = 1, a78: Int = 1, a79: Int = 1, a80: Int = 1, a81: Int = 1, a82: Int = 1, a83: Int = 1, a84: Int = 1, a85: Int = 1, a86: Int = 1, a87: Int = 1, a88: Int = 1, a89: Int = 1, a90: Int = 1, a91: Int = 1, a92: Int = 1, a93: Int = 1, a94: Int = 1, a95: Int = 1, a96: Int = 1, a97: Int = 1, a98: Int = 1, a99: Int = 1, a100: Int = 1, a101: Int = 1, a102: Int = 1, a103: Int = 1, a104: Int = 1, a105: Int = 1, a106: Int = 1, a107: Int = 1, a108: Int = 1, a109: Int = 1, a110: Int = 1, a111: Int = 1, a112: Int = 1, a113: Int = 1, a114: Int = 1, a115: Int = 1, a116: Int = 1, a117: Int = 1, a118: Int = 1, a119: Int = 1, a120: Int = 1, a121: Int = 1, a122: Int = 1, a123: Int = 1, a124: Int = 1, a125: Int = 1, a126: Int = 1, a127: Int = 1, a128: Int = 1, a129: Int = 1, a130: Int = 1, a131: Int = 1, a132: Int = 1, a133: Int = 1, a134: Int = 1, a135: Int = 1, a136: Int = 1, a137: Int = 1, a138: Int = 1, a139: Int = 1, a140: Int = 1, a141: Int = 1, a142: Int = 1, a143: Int = 1, a144: Int = 1, a145: Int = 1, a146: Int = 1, a147: Int = 1, a148: Int = 1, a149: Int = 1, a150: Int = 1, a151: Int = 1, a152: Int = 1, a153: Int = 1, a154: Int = 1, a155: Int = 1, a156: Int = 1, a157: Int = 1, a158: Int = 1, a159: Int = 1, a160: Int = 1, a161: Int = 1, a162: Int = 1, a163: Int = 1, a164: Int = 1, a165: Int = 1, a166: Int = 1, a167: Int = 1, a168: Int = 1, a169: Int = 1, a170: Int = 1, a171: Int = 1, a172: Int = 1, a173: Int = 1, a174: Int = 1, a175: Int = 1, a176: Int = 1, a177: Int = 1, a178: Int = 1, a179: Int = 1, a180: Int = 1, a181: Int = 1, a182: Int = 1, a183: Int = 1, a184: Int = 1, a185: Int = 1, a186: Int = 1, a187: Int = 1, a188: Int = 1, a189: Int = 1, a190: Int = 1, a191: Int = 1, a192: Int = 1, a193: Int = 1, a194: Int = 1, a195: Int = 1, a196: Int = 1, a197: Int = 1, a198: Int = 1, a199: Int = 1, a200: Int = 1, a201: Int = 1, a202: Int = 1, a203: Int = 1, a204: Int = 1, a205: Int = 1, a206: Int = 1, a207: Int = 1, a208: Int = 1, a209: Int = 1, a210: Int = 1, a211: Int = 1, a212: Int = 1, a213: Int = 1, a214: Int = 1, a215: Int = 1, a216: Int = 1, a217: Int = 1, a218: Int = 1, a219: Int = 1, a220: Int = 1, a221: Int = 1, a222: Int = 1, a223: Int = 1, a224: Int = 1, a225: Int = 1, a226: Int = 1, a227: Int = 1, a228: Int = 1, a229: Int = 1, a230: Int = 1, a231: Int = 1, a232: Int = 1, a233: Int = 1, a234: Int = 1, a235: Int = 1, a236: Int = 1, a237: Int = 1, a238: Int = 1, a239: Int = 1, a240: Int = 1, a241: Int = 1, a242: Int = 1, a243: Int = 1, a244: Int = 1, a245: Int = 1, a246: Int = 1, a247: Int = 1, a248: Int = 1, a249: Int = 1,  a250: Int = 1,  a251: Int = 1,  a252: Int = 1,  a253: Int = 1,  a254: Int = 1) { println(1) }
>>> f()
1
>>> 

Java

256個以上のパラメータがあると、定義することもできません。

jshell> void f(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, int a14, int a15, int a16, int a17, int a18, int a19, int a20, int a21, int a22, int a23, int a24, int a25, int a26, int a27, int a28, int a29, int a30, int a31, int a32, int a33, int a34, int a35, int a36, int a37, int a38, int a39, int a40, int a41, int a42, int a43, int a44, int a45, int a46, int a47, int a48, int a49, int a50, int a51, int a52, int a53, int a54, int a55, int a56, int a57, int a58, int a59, int a60, int a61, int a62, int a63, int a64, int a65, int a66, int a67, int a68, int a69, int a70, int a71, int a72, int a73, int a74, int a75, int a76, int a77, int a78, int a79, int a80, int a81, int a82, int a83, int a84, int a85, int a86, int a87, int a88, int a89, int a90, int a91, int a92, int a93, int a94, int a95, int a96, int a97, int a98, int a99, int a100, int a101, int a102, int a103, int a104, int a105, int a106, int a107, int a108, int a109, int a110, int a111, int a112, int a113, int a114, int a115, int a116, int a117, int a118, int a119, int a120, int a121, int a122, int a123, int a124, int a125, int a126, int a127, int a128, int a129, int a130, int a131, int a132, int a133, int a134, int a135, int a136, int a137, int a138, int a139, int a140, int a141, int a142, int a143, int a144, int a145, int a146, int a147, int a148, int a149, int a150, int a151, int a152, int a153, int a154, int a155, int a156, int a157, int a158, int a159, int a160, int a161, int a162, int a163, int a164, int a165, int a166, int a167, int a168, int a169, int a170, int a171, int a172, int a173, int a174, int a175, int a176, int a177, int a178, int a179, int a180, int a181, int a182, int a183, int a184, int a185, int a186, int a187, int a188, int a189, int a190, int a191, int a192, int a193, int a194, int a195, int a196, int a197, int a198, int a199, int a200, int a201, int a202, int a203, int a204, int a205, int a206, int a207, int a208, int a209, int a210, int a211, int a212, int a213, int a214, int a215, int a216, int a217, int a218, int a219, int a220, int a221, int a222, int a223, int a224, int a225, int a226, int a227, int a228, int a229, int a230, int a231, int a232, int a233, int a234, int a235, int a236, int a237, int a238, int a239, int a240, int a241, int a242, int a243, int a244, int a245, int a246, int a247, int a248, int a249, int a250, int a251, int a252, int a253, int a254,  int a255) { System.out.println(1); }
|  Error:
|  too many parameters
|  void f(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, int a14, int a15, int a16, int a17, int a18, int a19, int a20, int a21, int a22, int a23, int a24, int a25, int a26, int a27, int a28, int a29, int a30, int a31, int a32, int a33, int a34, int a35, int a36, int a37, int a38, int a39, int a40, int a41, int a42, int a43, int a44, int a45, int a46, int a47, int a48, int a49, int a50, int a51, int a52, int a53, int a54, int a55, int a56, int a57, int a58, int a59, int a60, int a61, int a62, int a63, int a64, int a65, int a66, int a67, int a68, int a69, int a70, int a71, int a72, int a73, int a74, int a75, int a76, int a77, int a78, int a79, int a80, int a81, int a82, int a83, int a84, int a85, int a86, int a87, int a88, int a89, int a90, int a91, int a92, int a93, int a94, int a95, int a96, int a97, int a98, int a99, int a100, int a101, int a102, int a103, int a104, int a105, int a106, int a107, int a108, int a109, int a110, int a111, int a112, int a113, int a114, int a115, int a116, int a117, int a118, int a119, int a120, int a121, int a122, int a123, int a124, int a125, int a126, int a127, int a128, int a129, int a130, int a131, int a132, int a133, int a134, int a135, int a136, int a137, int a138, int a139, int a140, int a141, int a142, int a143, int a144, int a145, int a146, int a147, int a148, int a149, int a150, int a151, int a152, int a153, int a154, int a155, int a156, int a157, int a158, int a159, int a160, int a161, int a162, int a163, int a164, int a165, int a166, int a167, int a168, int a169, int a170, int a171, int a172, int a173, int a174, int a175, int a176, int a177, int a178, int a179, int a180, int a181, int a182, int a183, int a184, int a185, int a186, int a187, int a188, int a189, int a190, int a191, int a192, int a193, int a194, int a195, int a196, int a197, int a198, int a199, int a200, int a201, int a202, int a203, int a204, int a205, int a206, int a207, int a208, int a209, int a210, int a211, int a212, int a213, int a214, int a215, int a216, int a217, int a218, int a219, int a220, int a221, int a222, int a223, int a224, int a225, int a226, int a227, int a228, int a229, int a230, int a231, int a232, int a233, int a234, int a235, int a236, int a237, int a238, int a239, int a240, int a241, int a242, int a243, int a244, int a245, int a246, int a247, int a248, int a249, int a250, int a251, int a252, int a253, int a254,  int a255) { System.out.println(1); }
|  ^----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------^
|  modified method f(int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int), however, it cannot be referenced until this error is corrected: 
|      too many parameters
|      void f(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, int a14, int a15, int a16, int a17, int a18, int a19, int a20, int a21, int a22, int a23, int a24, int a25, int a26, int a27, int a28, int a29, int a30, int a31, int a32, int a33, int a34, int a35, int a36, int a37, int a38, int a39, int a40, int a41, int a42, int a43, int a44, int a45, int a46, int a47, int a48, int a49, int a50, int a51, int a52, int a53, int a54, int a55, int a56, int a57, int a58, int a59, int a60, int a61, int a62, int a63, int a64, int a65, int a66, int a67, int a68, int a69, int a70, int a71, int a72, int a73, int a74, int a75, int a76, int a77, int a78, int a79, int a80, int a81, int a82, int a83, int a84, int a85, int a86, int a87, int a88, int a89, int a90, int a91, int a92, int a93, int a94, int a95, int a96, int a97, int a98, int a99, int a100, int a101, int a102, int a103, int a104, int a105, int a106, int a107, int a108, int a109, int a110, int a111, int a112, int a113, int a114, int a115, int a116, int a117, int a118, int a119, int a120, int a121, int a122, int a123, int a124, int a125, int a126, int a127, int a128, int a129, int a130, int a131, int a132, int a133, int a134, int a135, int a136, int a137, int a138, int a139, int a140, int a141, int a142, int a143, int a144, int a145, int a146, int a147, int a148, int a149, int a150, int a151, int a152, int a153, int a154, int a155, int a156, int a157, int a158, int a159, int a160, int a161, int a162, int a163, int a164, int a165, int a166, int a167, int a168, int a169, int a170, int a171, int a172, int a173, int a174, int a175, int a176, int a177, int a178, int a179, int a180, int a181, int a182, int a183, int a184, int a185, int a186, int a187, int a188, int a189, int a190, int a191, int a192, int a193, int a194, int a195, int a196, int a197, int a198, int a199, int a200, int a201, int a202, int a203, int a204, int a205, int a206, int a207, int a208, int a209, int a210, int a211, int a212, int a213, int a214, int a215, int a216, int a217, int a218, int a219, int a220, int a221, int a222, int a223, int a224, int a225, int a226, int a227, int a228, int a229, int a230, int a231, int a232, int a233, int a234, int a235, int a236, int a237, int a238, int a239, int a240, int a241, int a242, int a243, int a244, int a245, int a246, int a247, int a248, int a249, int a250, int a251, int a252, int a253, int a254,  int a255) { System.out.println(1); }
|      ^----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------^

jshell> void f(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, int a14, int a15, int a16, int a17, int a18, int a19, int a20, int a21, int a22, int a23, int a24, int a25, int a26, int a27, int a28, int a29, int a30, int a31, int a32, int a33, int a34, int a35, int a36, int a37, int a38, int a39, int a40, int a41, int a42, int a43, int a44, int a45, int a46, int a47, int a48, int a49, int a50, int a51, int a52, int a53, int a54, int a55, int a56, int a57, int a58, int a59, int a60, int a61, int a62, int a63, int a64, int a65, int a66, int a67, int a68, int a69, int a70, int a71, int a72, int a73, int a74, int a75, int a76, int a77, int a78, int a79, int a80, int a81, int a82, int a83, int a84, int a85, int a86, int a87, int a88, int a89, int a90, int a91, int a92, int a93, int a94, int a95, int a96, int a97, int a98, int a99, int a100, int a101, int a102, int a103, int a104, int a105, int a106, int a107, int a108, int a109, int a110, int a111, int a112, int a113, int a114, int a115, int a116, int a117, int a118, int a119, int a120, int a121, int a122, int a123, int a124, int a125, int a126, int a127, int a128, int a129, int a130, int a131, int a132, int a133, int a134, int a135, int a136, int a137, int a138, int a139, int a140, int a141, int a142, int a143, int a144, int a145, int a146, int a147, int a148, int a149, int a150, int a151, int a152, int a153, int a154, int a155, int a156, int a157, int a158, int a159, int a160, int a161, int a162, int a163, int a164, int a165, int a166, int a167, int a168, int a169, int a170, int a171, int a172, int a173, int a174, int a175, int a176, int a177, int a178, int a179, int a180, int a181, int a182, int a183, int a184, int a185, int a186, int a187, int a188, int a189, int a190, int a191, int a192, int a193, int a194, int a195, int a196, int a197, int a198, int a199, int a200, int a201, int a202, int a203, int a204, int a205, int a206, int a207, int a208, int a209, int a210, int a211, int a212, int a213, int a214, int a215, int a216, int a217, int a218, int a219, int a220, int a221, int a222, int a223, int a224, int a225, int a226, int a227, int a228, int a229, int a230, int a231, int a232, int a233, int a234, int a235, int a236, int a237, int a238, int a239, int a240, int a241, int a242, int a243, int a244, int a245, int a246, int a247, int a248, int a249, int a250, int a251, int a252, int a253,  int a254) { System.out.println(1); }
|  created method f(int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int)

jshell> f(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  1);
1

jshell> 

JavaScript

パラメータが256個以上でも関数定義できます・

function f(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, a60, a61, a62, a63, a64, a65, a66, a67, a68, a69, a70, a71, a72, a73, a74, a75, a76, a77, a78, a79, a80, a81, a82, a83, a84, a85, a86, a87, a88, a89, a90, a91, a92, a93, a94, a95, a96, a97, a98, a99, a100, a101, a102, a103, a104, a105, a106, a107, a108, a109, a110, a111, a112, a113, a114, a115, a116, a117, a118, a119, a120, a121, a122, a123, a124, a125, a126, a127, a128, a129, a130, a131, a132, a133, a134, a135, a136, a137, a138, a139, a140, a141, a142, a143, a144, a145, a146, a147, a148, a149, a150, a151, a152, a153, a154, a155, a156, a157, a158, a159, a160, a161, a162, a163, a164, a165, a166, a167, a168, a169, a170, a171, a172, a173, a174, a175, a176, a177, a178, a179, a180, a181, a182, a183, a184, a185, a186, a187, a188, a189, a190, a191, a192, a193, a194, a195, a196, a197, a198, a199, a200, a201, a202, a203, a204, a205, a206, a207, a208, a209, a210, a211, a212, a213, a214, a215, a216, a217, a218, a219, a220, a221, a222, a223, a224, a225, a226, a227, a228, a229, a230, a231, a232, a233, a234, a235, a236, a237, a238, a239, a240, a241, a242, a243, a244, a245, a246, a247, a248, a249, a250, a251, a252, a253, a254, a255) { console.log(1); }
f(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル2章 学習ログ

続きです

Railsチュートリアル1章 学習ログ
https://qiita.com/KoDoKu77/items/12c1e7197ba69044b1aa

Scaffoldでなんやかんや

micropostsモデルを作った後でビューを確認していたら、変なフィールドができてしまいました。

スクリーンショット 2019-02-11 15.01.03.png

schema.rb
  create_table "microposts", force: :cascade do |t|
    t.text "content"
    t.string "user_id"
    t.string "integer"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

integer型を指定したつもりが、intergerというカラムを作ってしまっています...
どうやら、scaffoldで間違った引数を与えてしまったようです。

rails g scaffold Micropost content:text user_id: integer

空白一つ開けちゃってますね...
マイグレーションファイルを作って、間違った部分を修正します。

$ rails g migration change_microposts_column
xxxxx_change_microposts_column.rb
class ChangeMicropostsColumn < ActiveRecord::Migration[5.1]
  def change
    change_column(:microposts, :user_id, integer)
    remove_column(:microposts, :integer)
  end
end

user_idカラムの型をinteger型へ変更し、
integerというカラムを削除します。

$ rails db:migrate

マイグレーションを実行して、テーブルを変更します。

schema.rb
  create_table "microposts", force: :cascade do |t|
    t.text "content"
    t.integer "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

上手くいきました。
その他、各ビューを修正。

Herokuへデプロイ

https://guarded-fortress-38244.herokuapp.com/

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

[Devise] パスワードを入力せずにユーザー情報を編集する

はじめに

deviseを使ってユーザーの登録情報を編集する際にデフォルトのままだと
パスワードの入力を求められます。しかし、一々ユーザーにパスワードの入力を
求めるのはユーザーフレンドリーではないので、修正していきます。

環境

ruby 2.5.3
rails 5.2.2
devise 4.6.0

Deviseを使う準備

アプリの作成

rails new devise

deviseのインストール

GEMFILE
gem 'devise'

上記を追加したらbundle installします。
次に、deviseのファイルを生成します。

rails generate devise:install

すると、次のメッセージが表示されますのでとりあえず
下記のメッセージ1~4の通りにセットアップしてみましょう。

Running via Spring preloader in process 3919
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

セットアップ

(1)

config/environments/development.rb
# 追記する
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

(2)
localhost:3000/にアクセスした時のページの設定を行います。
今のままだと何もページが作成されていないので、ここではPagesControllerを作ります。

ターミナル
rails generate controller Pages index
routes.rb
# get 'pages/index'
root 'pages#index'

これでrootパスの設定は完了です。

(3)
フラッシュメッセージを表示するようにviewを修正します。

application.html.erb
<body>
    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>
    <%= yield %>
 </body>

とりあえずこれでいいでしょう。

(4)
deviseのviewをカスタマイズするには以下のコマンドを実行して
viewファイルを作成する必要があります。

rails generate devise:views
Running via Spring preloader in process 4256
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

これでセットアップは完了です。

モデルの作成

登録情報を編集するユーザーを作成していきます。
まず、次のコマンドを実行します。

rails generate devise User
Running via Spring preloader in process 4296
      invoke  active_record
      create    db/migrate/20190211043147_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

モデルとマイグレーションファイルは次のようになってます。

user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end
migration
# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

ここでは、ユーザーのメールアドレスをパスワードの入力なしで、
変更することを目標とするので何も変更しないで、migrateします。

rails db:migrate

ユーザー情報を登録

まず、ユーザーを登録します。
登録画面にアクセスするためにルーティングを確認しましょう。

routes.rb
devise_for :user

上記がdeviseのルーティングを提供しています。

rails routes
Prefix Verb                      URI Pattern                                                                             Controller#Action
         new_user_session GET    /users/sign_in(.:format)                                                                 devise/sessions#new
             user_session POST   /users/sign_in(.:format)                                                                 devise/sessions#create
     destroy_user_session DELETE /users/sign_out(.:format)                                                                devise/sessions#destroy
        new_user_password GET    /users/password/new(.:format)                                                            devise/passwords#new
       edit_user_password GET    /users/password/edit(.:format)                                                           devise/passwords#edit
            user_password PATCH  /users/password(.:format)                                                                devise/passwords#update
                          PUT    /users/password(.:format)                                                                devise/passwords#update
                          POST   /users/password(.:format)                                                                devise/passwords#create
 cancel_user_registration GET    /users/cancel(.:format)                                                                  devise/registrations#cancel
    new_user_registration GET    /users/sign_up(.:format)                                                                 devise/registrations#new
   edit_user_registration GET    /users/edit(.:format)                                                                    devise/registrations#edit
        user_registration PATCH  /users(.:format)                                                                         devise/registrations#update
                          PUT    /users(.:format)                                                                         devise/registrations#update
                          DELETE /users(.:format)                                                                         devise/registrations#destroy
                          POST   /users(.:format)                                                                         devise/registrations#create
                     root GET    /                                                                                        pages#index
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create

パスワードを入力せずにユーザー情報を編集する

まず、編集画面にアクセスして、パスワードを入力せずに更新すると
次のようなエラーメッセージが表示されるかと思います。

Current password can't be blank

このpasswordのvalidationをスキップするためにdeviseをカスタマイズしていきます。

Controllerを生成する

deviseのcontrollerをカスタマイズするには、次のコマンドを実行して
自身のcontrollerを作成する必要があります。

rails generate devise:controllers users
Running via Spring preloader in process 4601
      create  app/controllers/users/confirmations_controller.rb
      create  app/controllers/users/passwords_controller.rb
      create  app/controllers/users/registrations_controller.rb
      create  app/controllers/users/sessions_controller.rb
      create  app/controllers/users/unlocks_controller.rb
      create  app/controllers/users/omniauth_callbacks_controller.rb
===============================================================================

Some setup you must do manually if you haven't yet:

  Ensure you have overridden routes for generated controllers in your routes.rb.
  For example:

    Rails.application.routes.draw do
      devise_for :users, controllers: {
        sessions: 'users/sessions'
      }
    end

===============================================================================

routes.rbを修正する

先ほど生成したcontrollerを利用するため次のように修正してください。

routes.rb
devise_for :users, controllers: { registrations: 'users/registrations' }

update_resourceメソッドをオーバーライドする

registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController

  protected
  # 追記する
  def update_resource(resource, params)
    resource.update_without_password(params)
  end
end

current_passwordフォームを削除する

views/devise/registrations/edit.html.erb
<div class="field">
    <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
    <%= f.password_field :current_password, autocomplete: "current-password" %>
 </div>

こちらのフォームを削除しましょう。

編集してみる

http://localhost:3000/users/editにアクセスして
メールアドレスを変更してみてください。
すると次のフラッシュメッセージが表示されるはずです。

Your account has been updated successfully.

これで、パスワードを入力しなくてもユーザーの登録情報を
編集することが可能になりました!

最後に

deviseはとても便利なのですが、カスタマイズするには色々修正が必要です。
いつかdevise大辞典作りたいな〜

参考

deviseのREADME.md

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

RailsのActiveRecord::FinderMethodsのSQLクエリ発行の有無について調べる

環境

Ruby 2.5
Rails 5.2.1

目的

Railsでアプリケーションを書く時、このメソッドはクエリを発行するかどうか、度々調べていて、ちゃんと覚えきれていないので、ちょっと調べようと思い、まとめてみることにしました。
ActiveRecord::FinderMethodsに絞ったのは読みやすそうだなと思ったからです。思ったより長くなり完全には調べきれるには気力を消耗しすぎたので、間違っている点があればご指摘頂けると幸いです。

参考

ActiveRecord::FinderMethods
Ruby on Rails API

まとめ表

記事本文はかなり長くなったので結果だけ見たい方はこちら。
(例外発生時のクエリ有無は含んでいません。)

メソッド名 一般的用途でのクエリ発行 例外的な(一般的な用途でない)クエリ発行しない条件
take 下記の場合ではクエリ発行しない。
1. loadedなインスタンスに対する、take/take!呼び出し。
2. 一度引数なしのtake/take!を呼び出したインスタンスへの、二度目のtake/take!呼び出し。
exist? 基本的にはクエリ発行。 下記の場合はクエリを発行しない。(いずれもfalseが返る)
1. 引数にfalseを渡した場合
2. limit(0)をチェーンしていた場合。
find 基本的にはクエリ発行。 下記の場合はクエリ発行しない。
1. loadedなのインスタンスに対して、blockつきで呼び出した場合(Enumerableのfindが呼ばれる)
2. 引数の先頭に空配列を渡した場合。(空配列が返る)
find_by/find_by! 基本的にはクエリ発行。 例外的に下記の場合はクエリ発行しない。
1. loadedなインスタンスに対して、blank?がtrueになる引数を渡した場合
(ex: users.find_by(nil))
first/first! 下記の場合ではクエリ発行しない。
1. loadedなインスタンスに対する、first/first!呼び出し。
2. 一度引数なしのfirst/first!を呼び出したインスタンスへの、二度目のfirst/first!呼び出し。
下記の場合はクエリを発行しない。
(引数なしの場合はnilが、ありの場合は空配列が返る)
1. limit(0)をチェーンしていた場合。
second/second!
third/third!
fourth!/fourth
fifth/fifth!
forty_two/forty_two!
下記の場合ではクエリ発行しない。
1. loadedなインスタンスに対する、メソッド呼び出し。
2. 一度メソッドを呼び出したインスタンスへの、二度目の同一メソッド呼び出し。
下記の場合はクエリを発行しない。
1. 内部で呼び出されている数値以下の引数のlimitをチェーンしていた場合。
(ex: forty_twoであれば、41以下の引数をもつlimitをチェーンしていた場合)
last/last! 下記の場合ではクエリ発行しない。
1. loadedなインスタンスに対する、メソッド呼び出し。
second_to_last/second_to_last!
third_to_last/third_to_last!
下記の場合ではクエリ発行しない。
1. loadedなインスタンスに対する、メソッド呼び出し。

ActiveRecord::FinderMethodsにはどんなメソッドがあるのか?

まずActiveRecord::FinderMethodsにどんなメソッドがあるか調べていきます。rails consoleで確認すると全部で25個のメソッドがあることがわかります。(読みやすいように並びを変更しています。)

pry(main)> ActiveRecord::FinderMethods.instance_methods(false)
=> [:take, :take!, :exists?, :find, :find_by, :find_by!,
 :first, :first!, :second, :second!, :third, :third!,
 :fourth!, :fourth, :fifth, :fifth!, :forty_two, :forty_two!,
 :last, :last!, :second_to_last, :second_to_last!,
 :third_to_last, :third_to_last!,
 :raise_record_not_found_exception!]
pry(main)> ActiveRecord::FinderMethods.instance_methods(false).count
=> 25

:raise_record_not_found_exception!は例外をあげるメソッドなので、実質24個です。

ActiveRecord::Relationのloaded?メソッド

先のメソッドを一つずつ見ていこうと思うのですが、その前にActiveRecord::Relationのloaded?メソッドについて少し触れておきます。なぜこのメソッドに触れるのかというと、クエリ発行の有無に関わる場合が多いものだからです。

loaded?メソッドはクエリ発行をし、オブジェクトを取得したかどうかを確認するメソッドです。
(参考:ActiveRecord::Relationとは一体なんなのか)

loaded?メソッドの実態は、@loadedインスタンスへのアクセサーのaliasで、@loadedのBooleanを返すものとなっています。

module ActiveRecord
  class Relation
    attr_reader :table, :klass, :loaded, :predicate_builder
    alias :loaded? :loaded
  end
end

それでは@loadedにどのようにtrue/falseが設定されるかを見ていきます。まずinitialize時には、falseがセットされます。

def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
  ~中略~
  @loaded = false
  ~中略~
end

その後、loadが呼ばれるとloadedでない場合、内部的にはexec_queriesを呼び出しますが、この中で@loadedtrueが代入されます。

def load(&block)
  exec_queries(&block) unless loaded?

  self
end

private
  def exec_queries(&block)
    skip_query_cache_if_necessary do
      ~中略~
      @loaded = true
      @records
    end
  end

そのためloadedなものに対して、loadをかけてもクエリは発行されません。

一方、reloadなどが呼ばれると内部的には、resetloadが走りますが、resetでは@loadedにnilが代入されるので、次のloadでクエリが走り、再度@loadedtrueが入ります。

def reload
  reset
  load
end

def reset
  ~中略~
  @to_sql = @arel = @loaded = @should_eager_load = nil
  ~中略~
end

このようにクエリ実行すべきかどうかの確認を行なっているのがloaded?メソッドになります。

それではこれから一つ一つメソッドを下記で見ていきます。

take/take!

take及びtake!メソッドは下記のようになっています。take!takenilの場合に例外をあげる以外、違いはありません。

def take(limit = nil)
  limit ? find_take_with_limit(limit) : find_take
end

def take!
  take || raise_record_not_found_exception!
end

takeは引数がある場合は、find_take_with_limitを、ない場合はfind_takeを呼んでいます。これらはどのようになっているかというと下記になります。

def find_take
  if loaded?
    records.first
  else
    @take ||= limit(1).records.first
  end
end

def find_take_with_limit(limit)
  if loaded?
    records.take(limit)
  else
    limit(limit).to_a
  end
end

早速loaded?が出てきましたね。

loadedなインスタンスに対して、
find_takeの場合は、recordsの最初のインスタンス
  (records.firstArray#firstなのでクエリ発行しない。)
find_take_with_limitの場合は、recordsの先頭から引数個分の配列
  (同じくArray#take)
を取り出すことがわかります。

一方load済みでない場合は、find_takefind_take_with_limitで異なります。

find_takeの場合は、@takeでキャッシュしており、キャッシュがあればそれを、ない場合はlimit(1)のクエリ発行することがわかります。
一方、find_take_with_limitの場合はいずれの場合も毎回limitを発行します。

実際にテストデータで実験してみるとよくわかります。

pry(main)> users =  User.where(created_at: 2.months.ago..1.months.ago);
pry(main)> users.take;
[DEBUG] SELECT  `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL AND `users`.`created_at` BETWEEN '2018-12-10 15:04:02' AND '2019-01-10 15:04:02' LIMIT 1`
pry(main)> users.take;
pry(main)> users.take(2);
[DEBUG] SELECT  `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL AND `users`.`created_at` BETWEEN '2018-12-10 15:04:02' AND '2019-01-10 15:04:02' LIMIT 2`
pry(main)> users.take(2);
[DEBUG] SELECT  `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL AND `users`.`created_at` BETWEEN '2018-12-10 15:04:02' AND '2019-01-10 15:04:02' LIMIT 2

結果的にはクエリ発行されない条件は下記になります。

1. loadedなインスタンスに対する、take/take!呼び出し。
2. 一度引数なしのtake/take!を呼び出したインスタンスへの、二度目の引数なしのtake/take!呼び出し。

exist?

exists?メソッドは下記のようになっています。

 def exists?(conditions = :none)
  if Base === conditions
    raise ArgumentError, <<-MSG.squish
      You are passing an instance of ActiveRecord::Base to `exists?`.
      Please pass the id of the object by calling `.id`.
    MSG
  end

  return false if !conditions || limit_value == 0

  if eager_loading?
    relation = apply_join_dependency(eager_loading: false)
    return relation.exists?(conditions)
  end

  relation = construct_relation_for_exists(conditions)

  skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false
rescue ::RangeError
  false
end

結構長いですが、自分なりに翻訳すると下記であろうと思います。

 def exists?(conditions = :none)
  if Base === conditions
    # ActiveRecordのインスタンスが渡されたらエラーを吐く。
  end

  # 条件にfalseもしくは、チェーンでlimit(0)を渡していた場合、falseが返る。
  return false if !conditions || limit_value == 0

  if eager_loading?
    # eager_loadしていた場合、eager_loadしたものに対して、exists?する。
    # (eager_load先のものを条件にすることが可能。)
  end

  # relationを構築する。
  relation = construct_relation_for_exists(conditions)

  # 必要であれば、クエリキャッシュをスキップする。(skip_query_cache_if_necessaryの内容をみる限り、block内は実行される。)
  skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false
rescue ::RangeError
  false
end

そのため、exists?において、例外が出る場合を除いて、クエリが発行されない条件は下記の二つになります。いずれも例外的で普段使わないと思うので、基本的にクエリ発行があると考える方が自然ですね。

1. 引数にfalseを渡した場合
2. limit(0)をチェーンしていた場合。

実際に実験してみると確かにSQLは発行されていません。

pry(main)> User.exists?;
[DEBUG] SELECT  1 AS one FROM `users` WHERE `users`.`deleted_at` IS NULL LIMIT 1`
pry(main)> User.exists?(false);
pry(main)> User.limit(0).exists?;
pry(main)> User.all.limit(1).exists?;
[DEBUG] SELECT  1 AS one FROM `users` WHERE `users`.`deleted_at` IS NULL LIMIT 1

find

findメソッドは下記のようになっています。

def find(*args)
  return super if block_given?
  find_with_ids(*args)
end

findはこの部分は結構シンプルです。findはblockが渡された場合、Enumerableのfindとして動作します。その他の場合は、find_with_idsが呼ばれます。find_with_idsは下記のようになっています。

def find_with_ids(*ids)
  raise UnknownPrimaryKey.new(@klass) if primary_key.nil?

  expects_array = ids.first.kind_of?(Array)
  return [] if expects_array && ids.first.empty?

  ids = ids.flatten.compact.uniq

  model_name = @klass.name

  case ids.size
  when 0
    error_message = "Couldn't find #{model_name} without an ID"
    raise RecordNotFound.new(error_message, model_name, primary_key)
  when 1
    result = find_one(ids.first)
    expects_array ? [ result ] : result
  else
    find_some(ids)
  end
rescue ::RangeError
  error_message = "Couldn't find #{model_name} with an out of range ID"
  raise RecordNotFound.new(error_message, model_name, primary_key, ids)
end

これもざっくり自分なりに翻訳すると下記になります。

def find_with_ids(*ids)
  # primary_keyがnilの場合、例外をあげる。

  # 渡されたidsのうち、先頭が配列かつ、空の場合に空配列を返す。
  expects_array = ids.first.kind_of?(Array)
  return [] if expects_array && ids.first.empty?
  # 配列を整理
  ids = ids.flatten.compact.uniq

  model_name = @klass.name

  case ids.size
  when 0
    # 整理した配列の中身が空だった場合、(ex: User.find(nil, nil)を渡した場合)例外をあげる。
  when 1
    # find_oneを呼び、引数が配列だった場合はインスタンス一つの配列で(ex: User.find([2]))、
    # 単数だった場合はインスタンスを返す。
    result = find_one(ids.first)
    expects_array ? [ result ] : result
  else
    # find_someを呼ぶ。
    find_some(ids)
  end
rescue ::RangeError
  # RangeErrorをrescue
end

find_oneでは、中でprimary_keyを条件としたwhereと引数なしのtakeが呼ばれており、条件ありのwhereをチェーンしているので、クエリが発行されます。
find_someでは、中でも同じく条件ありのwhereto_aが呼ばれており、クエリが発行されます。

find_with_idsではメソッドの先頭部分で、渡されたidsのうち、先頭が配列かつ、空の場合に空配列を返すようになっています。そのためこの場合はクエリ発行前に空配列が返るので、クエリは発行されません。実際に実験してみると確認できます。

pry(main)> User.find([1],2,3,4);
[DEBUG]   User Load (3.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL AND `users`.`id` IN (1, 2, 3, 4)`
pry(main)> User.find([],2,3,4);
pry(main)> User.find([],2,3,4)
=> []

結論として、findにおいて、例外が出る場合を除いて、クエリが発行されない条件は下記になります。

1. loadedなインスタンスに対して、blockつきで呼び出した場合(Enumerableのfind)
2. 引数の先頭に空配列を渡した場合。(空配列が返る)

find_by/find_by!

find_by及びfind_by!メソッドは下記のようになっています。find_byfind_by!の違いは例外時を除くとtaketake!かだけですね。

def find_by(arg, *args)
  where(arg, *args).take
rescue ::RangeError
  nil
end

def find_by!(arg, *args)
  where(arg, *args).take!
rescue ::RangeError
  raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
                           @klass.name, @klass.primary_key)
end

この場合はwhereに対してtakeをチェーンしているので、基本的にはクエリが発行されます。しかし、wheretakeの性質からある場合についてはクエリが発行されません。

takeについては既に見たので、whereについて見てみましょう。whereは下記のようになっています。

def where(opts = :chain, *rest)
  if :chain == opts
    WhereChain.new(spawn)
  elsif opts.blank?
    self
  else
    spawn.where!(opts, *rest)
  end
end

whereは結構複雑なので深入りはしませんが、一点着目して欲しいポイントがあります。それはopts.blank?の場合、selfを返すということです。ここではselfを返すだけなのでもちろんクエリ発行はしません。そして、takeはロード済みである場合はクエリ発行しないのでした。

つまり、load済みのものに対して、find_byの引数にblank?なものを渡せばクエリ発行はされません。実際に実験したのが下記です。

pry(main)> users =  User.where(created_at: 2.months.ago..1.months.ago).load;
pry(main)> users.find_by(nil);
pry(main)> users.find_by([]);

使い道は全くないですが、面白いです。

find_by/find_by!の場合、クエリが発行されない条件は下記です。

1. loadedなインスタンスに対して、blank?がtrueになる引数を渡した場合(users.find_by(nil)など)

first/first!

さて番号系メソッドのfirst/first!です。他の番号系メソッドと違い、first/first!だけ引数に数値を取れます。

内容的には下記のようになります。first!firstnilの場合に例外をあげるだけですね。

def first(limit = nil)
  if limit
    find_nth_with_limit(0, limit)
  else
    find_nth 0
  end
end

def first!
  first || raise_record_not_found_exception!
end

firstは引数がある場合とない場合で呼び出すメソッドが異なっています。それらのメソッドは下記になります。

def find_nth(index)
  @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
end

def find_nth_with_limit(index, limit)
  if loaded?
    records[index, limit] || []
  else
    relation = ordered_relation

    if limit_value
      limit = [limit_value - index, limit].min
    end

    if limit > 0
      relation = relation.offset(offset_index + index) unless index.zero?
      relation.limit(limit).to_a
    else
      []
    end
  end
end

find_nthfind_nth_with_limitを使用しており、なおかつ結果をインスタンス変数に格納していますね。find_nth_with_limitを自分なりに翻訳すると下記になります。

def find_nth_with_limit(index, limit)
  if loaded?
    # loadedの場合、recordsから特定のindexから特定数取り出す。nilの場合は空配列。
  else
    relation = ordered_relation

    if limit_value
      # limitメソッドを間に挟んでいた場合、limitメソッドの引数からindexを引いたものとfirstの引数の小さい方をlimitとして取る。
    end

    if limit > 0
      # offset_indexとindex数を足して、offsetする
      relation = relation.offset(offset_index + index) unless index.zero?
      relation.limit(limit).to_a
    else
      # limitが0以下の場合は空配列を返す
    end
  end
end

上記からfirstのクエリ発行しない条件は下記になります。

1. loadedなインスタンスに対する、first/first!呼び出し。
2. 一度引数なしのfirst/first!を呼び出したインスタンスへの、二度目の引数なしのfirst/first!呼び出し。
3. limit(0)をチェーンしていた場合。

second/second!/third/third!/fourth!/fourth/fifth/fifth!/forty_two/forty_two!

さて、first以外の番号系のメソッドですね。forty_twoだけ少し謎いですね…
(今回初めて知った)

こちらは基本的にはfirstと同じですが、引数は取れません。これらは内部的なメソッド呼び出しの引数が異なる以外は同じなので、サンプルにforty_two/forty_two!をみてみましょう。

def forty_two
  find_nth 41
end

def forty_two!
  forty_two || raise_record_not_found_exception!
end

やっていることはfind_nthに特定番号の引数を渡しているだけですね。ほとんとfirstと同じです。そのため、クエリ発行しない条件は下記になります。

1. loadedなインスタンスに対する、メソッド呼び出し。
2. 一度メソッドを呼び出したインスタンスへの、二度目の同一メソッド呼び出し。
3. 内部で呼び出されている数値以下の引数のlimitをチェーンしていた場合。
      (ex: forty_twoであれば、41以下の引数をもつlimitをチェーンしていた場合)

last/last!

残りも少なくなってきました。お次はlast/last!です。メソッド内容は下記になります。last!nilの場合に例外をあげるだけですね。

    def last(limit = nil)
      return find_last(limit) if loaded? || has_limit_or_offset?

      result = ordered_relation.limit(limit)
      result = result.reverse_order!

      limit ? result.reverse : result.first
    end

    def last!
      last || raise_record_not_found_exception!
    end

lastはloadedもしくはlimitかoffsetがある場合と、そうでない場合で挙動が異なります。まず、そうでない場合をみていきましょう。

そうでない場合は内容的にはそれほど難しくなさそうですね。lastに引数がある場合、引数をlimit数としてセットし、reverse_orderで内容を取得します。そして、引数なしの場合は、一つ目を、ありの場合は、reverse_orderで取得してきた内容をわざわざreverseメソッドで入れ替えています。おかげで、私たちはlast(2)と呼び出すと、最後から二つを順番通り(逆順ではなく)得ることができるということですね。そのためこの場合はクエリ発行されそうです。

続いて、loadedもしくはlimitoffsetがある場合を見ていきます。この場合は、find_lastメソッドを呼び出しています。

def find_last(limit)
  limit ? records.last(limit) : records.last
end

たったこれだけです。超シンプルです。さて、loadedの場合はArray#lastが呼ばれているのでクエリ発行はやはりされません。それではlimitoffsetの場合はどうでしょうか?。

この場合は、要するにクエリ発行の際にDESCで取ったりせずに単純にoffsetlimitで取ってきて、それを後ろから特定数取るということをしているだけなのです。実際のクエリを見るとわかりやすいです。

[70] pry(main)> User.last;
[DEBUG] SELECT  `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` DESC LIMIT 1`
[71] pry(main)> User.offset(2).limit(3).last;
[DEBUG] SELECT  `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL LIMIT 3 OFFSET 2

offsetlimitを使っている場合は、DESCが入っていませんよね。そのため、limitoffsetがある場合はクエリが発行されます。これらを踏まえるとクエリを発行しない条件は下記になります。

1. loadedなインスタンスに対する、メソッド呼び出し。

firstと違ってlastの場合は、インスタンス変数を保持していないので、二度目の呼び出しでもクエリは発行されますし、limit(0)をつけてもクエリ発行されます。

second_to_last/second_to_last!/third_to_last/third_to_last!

ようやく最後のメソッドです。最後まで見てくださって頂き誠にありがとうございます。私もこの時点で既に記事を書き始めて調査含め、6時間が経過しておりそろそろ終わりたいです。笑

メソッド内容を見てみましょう。second_to_last/second_to_last!third_to_last/third_to_last!は内部的には引数が異なるだけなので、second_to_last/second_to_last!を見ていきます。

def second_to_last
  find_nth_from_last 2
end

def second_to_last!
  second_to_last || raise_record_not_found_exception!
end

おなじみのsecond_to_last!の場合は、second_to_lastnilの場合に例外をあげるだけですね。それでは実態のメソッドのfind_nth_from_lastを見ていきます。

def find_nth_from_last(index)
  if loaded?
    records[-index]
  else
    relation = ordered_relation

    if equal?(relation) || has_limit_or_offset?
      relation.records[-index]
    else
      relation.last(index)[-index]
    end
  end
end

loadedの場合はおなじみですね。普通にrecordsから特定インデックスの場所を取り出すだけですね。そのためクエリ発行はされません。

問題は、loadedでない場合です、ifの部分が少しややこしいですが、これは要するに、現状保持しているrelationの状態がordered_relationと同じかどうか判定しています。ordered_ralationでは、orderの指定がなく、primary_keyが存在する場合は、primary_keyascorderするように指定しています。それ以外の場合はインスタンスをそのまま返しています。

def ordered_relation
  if order_values.empty? && primary_key
    order(arel_attribute(primary_key).asc)
  else
    self
  end
end

すなわち先のifの戻ると、何かしらのorderをしているもしくは、limitoffsetをしている場合は、そのorderlimit/offsetでクエリ発行をし、そこから特定インデックスの場所を取るということをしており、逆に指定がない場合は、primary_keyascでレコードを引いてきて、それに対して、特定インデックス分の引数を与えたlastメソッドを呼び出して、そこからさらに特定インデックスで取り出すということをしています。

    relation = ordered_relation

    if equal?(relation) || has_limit_or_offset?
      relation.records[-index]
    else
      relation.last(index)[-index]
    end

実際にクエリを見ると、orderなしの場合は、特定インデックス分のlimitが入っており、orderがありlimitがない場合は一旦全て引いてきていることがわかります。

pry(main)> User.second_to_last;
[DEBUG] SELECT  `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` DESC LIMIT 2`
pry(main)> User.order(id: :desc).second_to_last;
[DEBUG] SELECT `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` DESC

全レコードを取ってきているため、レコード数が多いテーブルに対して、気軽に引くと痛い目にあいます。lastはきちんとlimitをつけてくれるのですが、second_to_last等を使う場合は要注意です。

結果として、クエリを発行しない条件は下記になります。

1. loadedなインスタンスに対する、メソッド呼び出し。

lastと同じですね。

最後に

以上で終わりです。

想像以上にActiveRecordは奥深く、各メソッドの内部で呼び出されているメソッドは全然調べきれませんでした…

もし異なる点や実験してみたら違う結果が出たり他にクエリ発行しない条件があれば、ご指摘頂けると幸いです。

最後までお読み頂きありがとうございました。

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

Ruby にも関数言語ではやりの |> と <| がほしいよ!

class Object
  define_method :'|>' do |*args|
    send *args
  end

  define_method :'<|' do |arg1, *args|
    arg1.send self, *args
  end
end

# 本当は 'abc' |> upcase と書きたい
p 'abc'.method('|>').call :upcase   # => "ABC"
p :downcase.method('<|').call 'XYZ' # => "xyz"

Ruby は演算子の新規作成が無理なんで、こんなまどろっこしい方法で呼び出している。確か Jekyll の Liquid ってシステムでパイプを使ったリダイレクト?を実現していた記憶があるが、まり使い勝手が良いとはいえなかった。それに Liquid のやり方を Object で実装すると、SOLID の O と L であるオープン・クローズドの原則とリスコフの置換原則を破るので、あんまり行儀の良い方法ではないとは思うしね。

えー、そんなことはともかくそれで、次期 Ruby には搭載されてもいいと思うのですが、コア開発の皆様、2.6 で関数合成を導入したのだから、ここはひとつ、2.7 では |>, <| があると、クリスマスが楽しみになるんで、よろしくおねがいいたします。

かしこ

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

Windows10からRuby環境をアンインストールするメモ

環境

  • Windows 10 Home (ver.1803)
  • RubyInstaller2を使ってRubyをインストール済み

Rubyのアンインストール

Windowsの設定アプリアプリと機能から「ruby」で検索。
バージョンは異なるかもしれませんが以下のようなアプリが見つかります。

image.png

クリックしてアンインストールを選びます。

image.png

インストール先のディレクトリを削除

デフォルトではCドライブの直下に以下のようなRuby ***というディレクトリがあるので、これを削除します。
しかし、Rubyアンインストール直後だと「他のプロセスによって開かれています」みたいなメッセージが出て削除できないことがあるので、一度ログオフするか再起動するかしてから削除します。

image.png

これでRuby環境のアンインストールが完了です。

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

【Ruby】変数を文字列に入れ込む

変数に文字列を入れ込む

Ruby初心者のメモ書きです。

qiita.rb
name = "田中"
name2 = "斎藤"
puts "こんにちは、#{name}さん!"
puts 'こんにちは、#{name2}さん!'
#結果
こんにちは、田中さん!
こんにちは、#{name}さん!

シングルクオートで囲むと、変数は展開されずにそのまま出力される!!

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

PostScriptをテキスト編集して圧縮画像を埋め込み

高機能な画像編集ソフトの使い方ではなく、自力でPostScriptを編集してJPEGやPNGを埋め込む方法のメモ。

※数年前に書いたまま放置していたが、平成が終わる前に公開することにした。コードが拙い…。

したい理由

  • 手元のPostScriptの図に画像を追加して重ねたい
    • 図を描いたライブラリ1は外部画像の埋め込みに非対応(元々そういう用途でないので)
    • 汎用ソフトで編集すると大幅に書き換えられ、ライブラリ付属の編集ツールが一切使えなくなる
      • というより自分自身がPostScriptを理解できなくなる
  • 非圧縮のビットマップ画像ならライブラリを模倣して埋め込めるが、そのために圧縮画像を展開するのは気が進まない
    • ファイルサイズが無駄に増えてしまう
    • 元の形に再圧縮するのはたぶんどのソフトでも無理

要するに、素材本来の情報をなるべく維持して画像を追加したい。

output_clip.jpg

例:上の図は平成26年豪雪のときの衛星雲画像2(JPEG)と海面気圧3・海岸線・経緯線(PostScript)を重ねたもの。海岸線がきれいに一致しているので、正しく重ねられていそう。

考え方

PostScriptはデータをエンコード・デコードするフィルターを複数持っている。これを利用して、「ASCII化したデータ」(PSファイルに貼り付け)→「圧縮画像本来のバイナリデータ」(元画像から抽出したもの)→「展開した画像データ」(ビットマップに近いもの)とデコードさせる。

  • 画像
    • DCTDecode : JPEGデータの展開
    • FlateDecode : PNGなど、deflateで圧縮されたデータの展開
    • RLEDecode : 連長圧縮データの展開
  • ASCII化データの読み込み
    • Ascii85Decode : 4バイトのデータ(=256進数4桁)を "!-u" の範囲の文字5つ(=85進数5桁)で表す。約1.25倍に増加
    • AsciiHexDecode : 16進数の文字列で表す。約2倍に増加

データを展開し終わったら、それを適切な画像として解釈するための情報が必要になる。これを事前に画像ファイルから抜き出すことになる。

  • 色空間 : グレースケール・RGB・インデックスカラーなど
  • 色深度 : 普通は8ビット、インデックスカラーでは4, 2, 1も
  • 画像の幅と高さ

PostScriptはラスタ画像を描画する際、設定された座標上の1x1の領域に描こうとする。そのため、まともな大きさ・縦横比の画像を表示するには事前に座標変換を指定しておく必要がある。

サンプルコード

EPSに変換するコード。gsavegrestoreをコピペして座標変換をうまく設定すれば、PostScriptに画像を重ねることができる。

raster2ps.rb
class Raster2PS
    # 画像ファイルを指定してインスタンスを作成する
    # (必要なメソッドを実装したサブクラスを用いること)
    def initialize(filename)
        raise 'Not implemented' if self.instance_of?(Raster2PS)
        @filename = filename
        read_image
    end

    # 画像を埋め込んだPostScriptをファイルへ書き出す
    def write_image(filename)
        File.open(filename, 'w') do |f|
            f.puts <<-EOS
%!PS-Adobe-3.0 EPSF-3.0
%%Title: #{@filename}
%%Creator: raster2ps.rb
%%CreationDate: #{Time.now}
%%BoundingBox: 0 0 #{@width} #{@height}
%%DocumentData: Clean7Bit
%%LanguageLevel: 3
%%EndComments
%%BeginProlog
%%EndProlog
%%Page: 1 1
gsave

0 0 translate
0 rotate
#{@width} #{@height} scale

#{colorspace} setcolorspace

/Data currentfile /ASCII85Decode filter
#{image_filter} def
<< /ImageType 1
   /Width #{@width}
   /Height #{@height}
   /ImageMatrix [#{@width} 0 0 #{-@height} 0 #{@height}]
   /DataSource Data
   /BitsPerComponent #{@depth}
   /Decode [#{decode.join(" ")}]
   /Interpolate true
>> image
#{self.class.ascii85_encode(@data)}

grestore
%%EOF
            EOS
        end
    end

    private

    # @filenameを読み込み、@width, @height, @depth, @data, (適宜その他)へ保存する
    def read_image; end

    # PostScriptに埋め込む文字列を組み立てて返す
    def colorspace; end
    def image_filter; end
    def decode; end

    # バイナリ文字列をAdobe Ascii85形式の7bit文字列へエンコードする
    def self.ascii85_encode(str)
        m = (-str.length) % 4

        tuples = (str + "\x00" * m).unpack('N*')
        tuples.collect! do |tuple|
            5.times.inject([]) do |ary,i|
                tuple, mod = tuple.divmod(85)
                ary.unshift(mod + 0x21)
            end
        end
        tuples.flatten!
        tuples.pop(m)

        str = tuples.pack('C*')
        str.gsub!(/.{1,5}/) { |s| s == '!!!!!' ? 'z' : s }
        str.scan(/.{1,80}/).unshift('<~').push('~>').join("\n")
    end
end
jpeg2ps.rb
require './raster2ps'

class JPEG2PS < Raster2PS
    def read_image
        @data = File.read(@filename, mode: 'rb')
        raise 'Invalid JPEG' if @data[0,4] != "\xff\xd8\xff\xe0"

        n = @data.length
        i = 2
        while i < n
            mark, size = @data[i,4].unpack('a2n')
            if mark == "\xff\xc0" # SOF0
                @height, @width, @type = @data[i+4,size-2].unpack('xnnc')
                break
            end
            i += 2 + size
        end

        @depth = 8

        self
    end

    def colorspace
        case @type
        when 1 then '/DeviceGray'
        when 3 then '/DeviceRGB'
        when 4 then '/DeviceCMYK'
        else        raise "unknown JPEG type: #{@type}"
        end
    end

    def image_filter
        '/DCTDecode'
    end

    def decode
        [0, 1] * @type
    end
end
png2ps.rb
require './raster2ps'

class PNG2PS < Raster2PS
    def read_image
        @data = ''

        File.open(@filename, 'rb') do |f|
            raise 'Invalid PNG' if f.read(8) != "\x89PNG\r\n\x1a\n"

            until f.eof?
                size, name = f.read(8).unpack('Na4')
                bin = f.read(size)
                crc = f.read(4)

                case name
                when 'IHDR'
                    @width, @height, @depth, @type, = bin.unpack('N2C5')
                when 'PLTE'
                    @plte = bin
                when 'IDAT'
                    @data << bin
                end
            end
        end

        self
    end

    def colorspace
        case @type
        when 0 then '/DeviceGray'
        when 2 then '/DeviceRGB'
        when 3 then
            <<-EOS
/Palette currentfile #{@plte.length} string readhexstring 
#{@plte.pack('H*').gsub(/.{1,6}/, '#\0 ').scan(/.{1,64}/).join("\n")}
pop def
[/Indexed /DeviceRGB #{@plte.length / 3 - 1} Palette]
            EOS
        else        raise "unknown PNG type: #{@type}"
        end
    end

    def image_filter
        <<-EOS
<< /Predictor 10
   /Columns #{@width}
   /Colors #{decode.length / 2}
   /BitsPerComponent #{@depth}
>> /FlateDecode
        EOS
    end

    def decode
        case @type
        when 0 then [0, 1]
        when 2 then [0, 1] * 3
        when 3 then [0, 2 ** @depth - 1]
        else        raise "unknown PNG type: #{@type}"
        end
    end
end

補足

サンプルコードを読めば分かるかもしれないが、細かい点はこちらにメモ。

画像の読み込み

幅と高さ・色深度・色数などの情報および貼り付けるデータ本体を画像ファイルから取り出す。

JPEG

  • ファイルは "\xff\xd8\xff\xe0" で始まる
  • 各種ヘッダは、マーカー(2バイト)・サイズ(2バイト)・データ(n-2バイト)という構造
    • SOF0(マーカー "\xff\xc0")の中に、画像サイズや色の種類の情報がある
  • PSに貼り付けるデータはファイル全体でよい。本体を抽出する必要なし

PNG

  • ファイルは "\x89PNG\r\n\x1a\n" で始まる
  • 各チャンクは、サイズ(4バイト)・名前(4バイト)・データ(nバイト)・CRC(4バイト)という構造
    • IHDRの中に、画像サイズや色深度や色の種類の情報がある
    • PLTEはカラーパレットなので、あれば抜き出しておく
    • IDATは画像本体
  • PSに貼り付けるデータはIDATの中身。大きなデータは分割されているので、全て抜き出して連結する必要がある

画像設定

色空間

  • 基本的な場合は色の次元で決まり、 /DeviceGray, /DeviceRGB のように名前だけ指定すればいい
  • インデックスカラーの場合は複雑で、[/Indexed 色空間名 インデックス最大値(=色数-1) パレットデータ] と配列で与える

フィルター

  • JPEGは /DCTDecode だけで全て処理してくれる
  • PNGは汎用のZlibを利用しているので、画像のデコードであることを辞書で教える必要がある

/Decode

  • 画素の数値がとる範囲を指定する
    • 通常は0~1。次元の数だけ必要なので、RGBなら [0 1 0 1 0 1] と6要素の配列になる
    • インデックスの場合は色数ではなく、色深度によって決まる。例えば4ビットなら0~15

参考


  1. 地球流体電脳ライブラリ(DCL) v5.3.4.2 ※v6から出力形式が変更された 

  2. 高知大学気象情報頁より 

  3. MERRA再解析データより 

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

pigpioのrubyバインディングを作った。

置き場

https://github.com/nak1114/ruby-extension-pigpio

ドキュメント

https://nak1114.github.io/ruby-extension-pigpio/Pigpio/IF.html

はい。FileとThread以外の関数をバインドしたところで力尽きました。
今後は暇を見てはrubyらしい使い方が出来るようにラッピングクラス(Pigpio)を作っていきます。(Pigpioって打ちにくいですよね。)
後、Rspecも書かないといけないです。ダミーのpigpioライブラリを作ってラズパイが無くてもRspec出来るように環境を整えたので、あとは120個近い関数のテストを書くだけ。

きっかけ

rubyの拡張ライブラリの書き方を勉強する必要があったので、どうせならばと実用性のあるブツで勉強しようとつくりました。
おかげさまで、ほぼほぼ習得できたと思います。なので私の習得方法を書いてみたいと思います。

習得方法

拡張ライブラリ作成に必要な事は全て extension_ja に書いてありますが、何も知らない人がここを見ても理解できないと思います。
なので、理解できないなりに流し読みした後、適当にググったサイトを2つ、3つ写経して2時間くらいすると悟りがひらけます。悟りの中身は「ruby本体のCソースを読むべし」です。
悟りがひらけるころにはextension_jaを読んで中身を理解することができるので、これを読みながら自分なりに小さなライブラリを作るのを4時間くらい。
だいたい5,6時間もあれば拡張ライブラリをマスターできると思います。

習得できなかったこと

GCのトリガ

結局ruby.hに記載されたどの関数がGCトリガになるのか、最後まで不明でした。深くソースを追うのはちょっと躊躇われるので、ここは雰囲気で作ってます。多分、RB_GC_GUARDは多めに入っていると思います。

Moduleの特異メソッドの第一引数にVALUE selfが必要な理由

クラスメソッドはincludeでクラスのクラスメソッドになるからselfが必要と納得。
特異メソッドはどこにもくっつかないので、selfが参照されることは無い・・・はず。
これは多分全てをオブジェクトとして扱えて言語仕様が美しいという美的感覚かな?。

さいごに

当初の目的の勉強は達成したので、pigpioのバインディングの方はあまり進捗がなくなります。
なので、使いたいという人は自由に改変してください。PRくれたらコミットします。

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

config.i18n.fallbacksのメッセージについて

rails 5.2.2をインストールしたら標準出力の最後に以下のメッセージが表示された。

HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.
But that may break your application.

Please check your Rails app for 'config.i18n.fallbacks = true'.
If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be
'config.i18n.fallbacks = [I18n.default_locale]'.
If not, fallbacks will be broken in your app by I18n 1.1.x.

For more info see:
https://github.com/svenfuchs/i18n/releases/tag/v1.1.0

最近i18nがバージョンアップしたから表示されるようになったのかなと思いきや、現在のバージョンは1.5.3だった。今までずっとメッセージが表示されていたのかな・・・ずっと無視してた・・・。まあ、日本オンリーの私には関係なさそうだし。。。

訳してみる。

HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.
But that may break your application.

「注意してね。i18n 1.1でデフォルトロケールを除外するようにフォールバックを変更したよ。」
「だけどその変更が君のアプリをエラーで落とすかもしれないよ。」

うーん。意味がわからない。「除外する」とか、よくわからない。「But」も逆接で訳すと何かおかしい感じ。
とにかく、危ない問題ということだけはわかった。

Please check your Rails app for 'config.i18n.fallbacks = true'.
If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be
'config.i18n.fallbacks = [I18n.default_locale]'.
If not, fallbacks will be broken in your app by I18n 1.1.x.

「君のRailsアプリの'config.i18n.fallbacks = true'という設定をチェックしてね。」
「もしI18n (>= 1.1.0) かつ Rails (< 5.2.2) を使っているなら、'config.i18n.fallbacks = [I18n.default_locale]'という設定に変更してね。」
「もし使っていないのなら、君のアプリのフォールバックはI18n 1.1.x(1.1.0以上という意味かな)によってエラーになるよ。」

うーむ。このように訳すと、今回のようにI18n 1.5.3 (>= 1.1.0) かつ Rails 5.2.2 (>= 5.2.2)を使った場合は設定変更するという手段は取れず、エラーを回避できないってことなのかな。それとも5.2.2以上は問題ないのかな。

今まで日本語サイトしか作ってこなかったし、今後も作る予定もないから、個人的にはどうでもいい問題ではある。。。

For more info see:
https://github.com/svenfuchs/i18n/releases/tag/v1.1.0

「詳細はこのURLを見てね」
実際に見て見ると、タイトルは以下となっていた。

v1.1.0
BREAKING CHANGE: Fallbacks

v1.1.0 のリリースノートなのかな。「フォールバックについて、互換性のない変更がある」ということかな。書いてあることはgemインストール時に表示されたものとほぼ同じっぽい。

結局、意味がわからなかったので、RailsGuide 5.2.2 を見てみる。すると、'config.i18n.fallbacks'の書き方として3つあるようだ。

# You can set the option to true for using default locale as fallback, like so:
config.i18n.fallbacks = true
# Or you can set an array of locales as fallback, like so:
config.i18n.fallbacks = [:tr, :en]
# Or you can set different fallbacks for locales individually. For example, if you want to use :tr for :az and :de, :en for :da as fallbacks, you can do it, like so:
config.i18n.fallbacks = { az: :tr, da: [:de, :en] }
# or
config.i18n.fallbacks.map = { az: :tr, da: [:de, :en] }

なるほど。I18n (>= 1.1.0)からは1つ目の書き方ができないってことかな。だから2つ目の書き方をしろと。あーそういうことか。以下のメッセージの意味がわかった。。。(再掲)

HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.

「注意してね。i18n 1.1で、デフォルトロケールを設定するという設定は除外されるようにフォールバックを変更したよ。」

でも、結論として、Rails 5.2.2からは問題なく使えるんだよね? Rails 5.2.2で作成した場合でも、デフォルトで以下のように設定されているから。

 72   # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
 73   # the I18n.default_locale when a translation cannot be found).
 74   config.i18n.fallbacks = true

ということで、あとで実機確認をしなくては。これまでは全て机上です。

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

【Python】「マネーフォワード社内PRに見られるRubyの書き方について (3) 」をPythonに置き換えて考えてみた

マネーフォワード社内PRに見られるRubyの書き方について (3) | Money Forward Engineers' Blogの記事はRubyだけどPythonにも適用できそうだから、考えてみた。
また、Rubyは一切やったことがないため、解釈が間違っているかもしれません。もし間違っていたら、教えていただけると嬉しいです。

自分が気になったところだけ書いたので、書いていない部分は記事を見てください。

動作環境

Python 3.7.1

文字列の生成や検証

必要もないのに正規表現を使ってやろうとしていたり、特定のメソッドに固執してそれを乱用しているということに集約される
...
正規表現の乱用の例とメソッド別の不的確な用例を挙げていきます。

正規表現の乱用 (単一の文字列を表している)

「単一の文字列」とのマッチはre.split()(正規表現での分割)ではなく、str.split()(文字列での分割)を使う

>>> s = '1, 2, 3, 4'

# No:
>>> re.split(r',', s)
['1', ' 2', ' 3', ' 4']

# Yes:
>>> s.split(',')
['1', ' 2', ' 3', ' 4']

また、Rubyの場合String#splitに正規表現を渡すことができるが、Pythonのstr.split()に正規表現は渡せないため、re.split()を使う

str.find()re.search()を使わなくてよいところは、極力使わない

正規表現でのマッチの場合、re.match()だと先頭にしかマッチしないため、re.search()を使うこと

文字列内に文字が含まれているかはin演算子を使う

  • 文字列内に、ある文字(リテラル)が含まれているかのチェックは、in演算子を使う
  • 文字列内に、あるパターンが含まれているかのチェックは、re.search()を使う

文字列内に、ある文字が含まれているかの真偽値が欲しい場合、str.find()(マッチした文字の位置を返すメソッド)ではなく、obj in str(含まれているかどうかを返すin演算子)を使う。

ドキュメントにも書いてあった

>>> s = 'Hello world!'
# No:
>>> s.find('H') != -1
True

# Yes:
>>> 'H' in s
True

パターンの場合、re.search()しかないため何も考えずにre.search()を使う

# Yes:
>>> import re
>>> re.search(r'[hH]ello', s)
<re.Match object; span=(0, 5), match='hello'>

完全一致しているかどうかは==を使う

==を使えば一発で判断できる
わざわざ、^str$のように正規表現を使わなくても良い

# No:
>>> re.search(r'^hoge$', 'hoge')
<re.Match object; span=(0, 4), match='hoge'>

# Yes:
>>> 'hoge' == 'hoge'
True

先頭や末尾での一致はstrの関数を使う

先頭や末尾での一致は正規表現ではなく、str.startswith()str.endswith()を使う

# No:
>>> re.match(r'^sample', s) is not None
True

>>> re.search(r'.py$', s) is not None
True

# Yes:
>>> s.startswith('sample')
True

>>> s.endswith('.py')
True

str.split()re.split()の乱用

区切り文字が、文字列(リテラル)の場合、str.split()を使い、パターンの場合、re.split()を使う

文字や行への分解

str.split()が多く使われれる処理

  • (文字への分割)
  • 行への分割

Pythonでは、list()に文字列を渡すことで簡単に文字への分割ができる

>>> s = 'abc\ndef\n'

# Yes:
>>> list(s)
['a', 'b', 'c', '\n', 'd', 'e', 'f', '\n']

行への分割をするには、str.splitlines()を使う

>>> s = 'abc\ndef\n'

# No:
>>> s.split('\n')
['abc', 'def', '']

# Yes:
>>> s.splitlines()
['abc', 'def']
# また、改行を残したい場合、`keepends`引数に`True`を渡す
>>> s.splitlines(keepends=True)
['abc\n', 'def\n']

無駄な分割をしないようにする

もし、分割した要素の「最初」と「最後」のみ取得したい場合、「最初」と「最後」の要素さえ分割できればいいため、すべてを分割する必要はない

# No:
>>> s = 'foo##bar##baz##qux'
>>> s.split('##')
['foo', 'bar', 'baz', 'qux']

# 最初の要素
>>> s.split('##')[0]
'foo'

# 最後の要素
>>> s.split('##')[-1]
'qux'

'bar'(2つ目の要素)と'baz'(3つ目の要素)は一切使わないため、要素としてはいらない

無駄がないように処理をするには、2つの方法がある。

  1. str.split()(とstr.rsplit())の第2引数で分割を行う回数を指定する
  2. str.partition()(とstr.rpartition())を使い、最低限の分割しか行わないようにする

1つ目の方法の「str.split()(とstr.rsplit())の第2引数で分割を行う回数を指定する」でやってみる

# Yes:
# 最初の要素
#   第2引数で、分割回数を1回に指定している
>>> s.split('##', 1)
['foo', 'bar##baz##qux']
>>> s.split('##', 1)[0]
'foo'

# 最後の要素
>>> s.rsplit('##', 1)
['foo##bar##baz', 'qux']
>>> s.rsplit('##', 1)[-1]
'qux'

次に2つ目の方法の「str.partition()(とstr.rpartition())を使い、最低限の分割しか行わないようにする」でやってみる

# Yes:
# first
>>> s.partition('##')
('foo', '##', 'bar##baz##qux')
>>> s.partition('##')[0]
'foo'

# last
>>> s.rpartition('##')
('foo##bar##baz', '##', 'qux')
>>> s.rpartition('##')[-1]
'qux'

partition(sep)は、先頭に近い位置のsepで分割し、3つの要素のタプルを返す。1つ目はsepより前の文字列、2つ目はsepそのもの、3つ目はsepより後の文字列となっている。
また、rpartition(sep)は、末尾に近い位置のsepで分割し、3つの要素のタプルを返す。1つ目はsepより後ろの文字列、2つ目はsepそのもの、3つ目はsepより前の文字列となっている。

単純なパターンで書ける方を採用する

reモジュールにはsplit()findall()というメソッドがある

  • re.split()は、指定したパターンにマッチした文字列を区切り文字とし、分割した結果のリストを返す。
  • re.findall()は、指定したパターンにマッチする文字列のリストを返す。

もし、どちらかが複雑なパターンになってしまった場合には、簡単に理解できる方で書くようにする

>>> import re
>>> l = ['2019年2月1日', '2019-2-1', '2019/2/1']

# 複雑...
>>> [re.split(r'[年月日/-]', x) for x in l]
[['2019', '2', '1', ''], ['2019', '2', '1'], ['2019', '2', '1']]

# 簡単!
>>> [re.findall(r'\d+', s) for s in l]
[['2019', '2', '1'], ['2019', '2', '1'], ['2019', '2', '1']]

このように、分割する文字列([年月日/-])よりも、取得したい文字列(\d+)のほうが簡単に理解できるため、この場合、re.findall()を使うほうが良い。

re.replace()の乱用

1文字のパターンをバラバラに繰り返し使っている

1文字のパターンの置換を繰り返しているコードがあったとする

>>> s = "foo:bar,baz-gux"
>>> s.replace(';', ':').replace(',', '.').replace('-', '|')
'foo:bar.baz|gux'

大変...

Rubyでは、マッチ文字と置き換え文字の対応をした文字列を渡せるString#trというメソッドがあるらしい(Ruby全く知らないからグーグル先生にきいた...)

p "foo".tr("f", "X")      # => "Xoo"
p "foo".tr('a-z', 'A-Z')  # => "FOO"
p "FOO".tr('A-Z', 'a-z')  # => "foo"

Pythonには、RubyのString#trのようなメソッドなさそう...
それ通りに実装してみた

import re


def str_tr(s: str, old: str, new: str):
    for o, n in zip(old, new):
        s = re.sub(o, n, s)
    return s


str_tr("foo;bar,baz-gux", ";,-", ":.|")
# 'foo:bar.baz|gux'

一応できたけど、もっと良い書き方無いのかな...

まとめ

正規表現を使うのではなく、strのメソッドを使うことで簡単に書けることもある

参考文献

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

rails generateする時sqlite3の指定で躓いた話

環境

  • cloud9
  • Rails 5.2.2

controllerをgenerateしたかった

controlleを作ろうとrails generate。

$ rails generate controller search books

いやー、railsはこれがあるから良いですねー。
昔やってたStrutsなんて用意するファイルを全部手で作ってたから効率がぜんぜん違うわー。
さてさて、ズラズラっと出てきたから出来てー…

/usr/local/rvm/rubies/ruby-2.4.1/lib/ruby/gems/2.4.0/gems/bundler-1.16.5/lib/bundler/rubygems_integration.rb:408:in `block (2 levels) in replace_gem': Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile. (LoadError)
  :
 (以下スタックトレース)
  :

ないじゃん。
おもくそエラーじゃん。

答えはエラーに書いてある

ちゃんとしたフレームワークならちゃんとしたエラー文が出力されるので、まずはエラー文を眺めてみましょう。
すると、最後にこんな記載があるわけですな。

Make sure all dependencies are added to Gemfile.

英語が得意とは言えない人(=ぼく)でも「dependencies」を「Gemfile」でなんかしなきゃいけないことはなんとなーくわかりますね。
じゃあその直前に何が書いてあるかというと、

Missing a gem it depends on? can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0.

ざっくり和訳すると、「Genfileのsqlite3周りちゃんと見ろ」
わざわざバージョン指定まで入ってるので、Gemfileにコピペしときましょう。
現在のsqlite3をコメントアウトして、その下にコピってペーします。

gemfile
# Use sqlite3 as the database for Active Record
# gem 'sqlite3'
gem 'sqlite3', '~> 1.3.6'

保存したのを確認して、rails generateを再実行。

$ rails generate controller search books
Running via Spring preloader in process 12947
      create  app/controllers/search_controller.rb
       route  get 'search/books'
      invoke  erb
      create    app/views/search
      create    app/views/search/books.html.erb
      invoke  test_unit
      create    test/controllers/search_controller_test.rb
      invoke  helper
      create    app/helpers/search_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/search.coffee
      invoke    scss

今度はちゃんと動きました。めでたしめでたし。

参考資料

railsでおこったエラーについて

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

正規表現の先読み/後読みは前方確認/後方確認と変換すると分かりやすい

Ruby の正規表現の先読みと後読み、全然理解できません。

挙動は理解できます。単語の意味が分からないのです。全くピンと来ない。

仕方ないので、できる限り他の日本語に変換して噛み砕いてみようというのがこの記事の趣旨です。多分この手の記事は既出だと思うので、すでに仕様を理解されている方、自分なりに結論が出ている方には必要ないかもしれません。単に単語が分かりにくいだけなので。

TL;DR

  • 先読み … ある位置から先の文字列があるパターンにマッチするか
  • 後読み … ある位置より前の文字列があるパターンにマッチするか

言い換えると、先読み → 前方確認、後読み → 後方確認していると考えると少しは理解しやすいかもしれません(少なくとも自分は理解しやすいです)。が、しかしこの解釈の仕方は自分にとって重要です。

何が問題点か

そもそも読み方は、「先読み(さきよみ)」「後読み(あとよみ)」だと思うんですが、これを初見で聞いて何の話なのかピンと来ませんでした。

  • 「先読み(さきよみ)」 … 先を読む?先に読む?
  • 「後読み(あとよみ)」 … 後ろを読む?前を読む?後で読む?

先(さき)と後(あと)という読み方だけを見ると、「評価するタイミングのことを言っているのかな?」と思ってしまいます。ですが、ここで言っているのは「ある位置から先か後ろか」ということで、時間の話ではないようです。

時間の話か、位置の話か、これがはっきりしない(ように見える)ことが問題のように感じます。

Ruby の公式ドキュメントを確認する

ある位置から続く文字列が ある部分式にマッチするならばその位置にマッチする という正規表現を書くことができます。「ある位置から続く文字列(先読み、lookahead)/ある位置の手前までの文字列(後読み、lookbehind)」 と「マッチする(肯定、positive)/マッチしない(否定、negative)」 の組み合わせで4つのパターンがあります。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fregexp.html

パターン 名称(日本語) 名称(英語)
(?=pat) 肯定先読み positive lookahead
(?!pat) 否定先読み negative lookahead
(?<=pat) 肯定後読み positive lookbehind
(?<!pat) 否定後読み negative lookbehind

ドキュメントを読むと、英語の表現も含めて位置関係を示していることが分かると思います。

「ある位置から続く文字列」なのか
「ある位置の手前までの文字列」なのか

/(?<=<code>).+?(?=<\/code>)/

↑の例の場合、<code></code>に挟まれている文字列にマッチします(結構パフォーマンス悪そう…)。<code>から続くかつ</code>よりも手前の文字列を見ているっちゅーことですね。時間とかタイミングとか全く関係ないじゃん(いや評価するタイミングとか内部的には色々あるかもしれんけれども)。

先読み/後読みは前方確認/後方確認と変換する

まとめると、先(さき)とか後(あと)とか言っていますが、文字列の位置関係の話なので、なにか空間的な何かを感じる単語の方がしっくり来るのではないかと思います(諸説あり)。
もし、「なんだかな〜しっくりこねえなあ〜」と阿藤快状態になっている方いましたらご参考にしていただければ幸いです。
ちなみに「こんどこそわかる(肯|否)定(先|後)読み」という記事はとても参考になったので、ぜひ合わせて読んでみていただければと思います。

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