- 投稿日:2019-05-24T22:59:53+09:00
HomesteadでRuby on Railsの環境構築
Homesteadはlaravelの開発環境を簡単に構築できるパッケージだが、Ruby on Railsで必要なソフトも入っているため、Homesteadを使用したRuby on Railsの環境を構築していく。
今回はMacで、使用するDBはMySQLとする。
Homesteadとは?
Laravel公式のboxで、Vgrantを使用することで仮想マシンにLaravelの開発環境を簡単に構築することができるパッケージです。
PHPやWebサーバ、その他のサーバソフトウェアをローカルマシンにインストールする必要なく、開発環境を構築できます。導入されているソフトウェア
公式より以下のソフトが既に含まれています。(2019/5 現在)
- Ubuntu 18.04
- Git
- PHP 7.3
- PHP 7.2
- PHP 7.1
- Nginx
- MySQL
- lmmによるMySQLとMariaDBデータベーススナップショット
- Sqlite3
- PostgreSQL
- Composer
- Node (Yarn、Bower、Bower、Grunt、Gulpを含む)
- Redis
- Memcached
- Beanstalkd
- Mailhog
- avahi
- ngrok
- Xdebug
- XHProf / Tideways / XHGui
- wp-cli
- Minio
オプションで導入できるソフトウェア
- Apache
- Crystal & Lucky Framework
- Dot Net Core
- Elasticsearch
- Go
- MariaDB
- MongoDB
- Neo4j
- Oh My Zsh
- Ruby & Rails
- Webdriver & Laravel Dusk Utilities
- Zend Z-Ray
VirtualBoxとVagrantのインストール
VirtualBoxをインストール
仮想化ソフトは他にもいくつかあるそうですが「VirtualBox」を使用
インストールはこちらから >> https://www.virtualbox.org/
自分の使用しているOSを選択
【注意】
「機能拡張がブロックされました」と表示されたら「システム環境設定」→「セキュリティとプライバシー」から読み込みを許可しましょうVagrantをインストール
Vagrantとは仮想化ソフト(VirtualBox)を使用し、コマンドを入力することで仮想環境を管理したり構築したりすることができるツールです。
インストールはこちらから >> https://www.vagrantup.com/
自分の使用しているOSを選択
ダウンロードしたらバージョンを確認してみる。
$ vagrant --version Vagrant 2.2.4これで2.2.4のVagrantがインストールされました。
Homesteadのboxをインストール
boxとはOSのディスクイメージファイルで、これを追加することで仮想環境を作成できる。
https://app.vagrantup.com/laravel/boxes/homestead
上記のリンクの公式を確認すると「laravel/homestead」があるのでこれを取得する。
VagrantCloudにあるboxを追加する場合は名前だけでも良い。
$ vagrant box add laravel/homestead
実行するとproviderは何かと聞かれるので「virtualbox」の「3」を選択する。
結構時間がかかるので終わるまで待つ。$ vagrant box add laravel/homestead ==> box: Loading metadata for box 'laravel/homestead' box: URL: https://vagrantcloud.com/laravel/homestead This box can work with multiple providers! The providers that it can work with are listed below. Please review the list and choose the provider you will be working with. 1) hyperv 2) parallels 3) virtualbox 4) vmware_desktop Enter your choice: 3 ==> box: Adding box 'laravel/homestead' (v7.2.1) for provider: virtualbox box: Downloading: https://vagrantcloud.com/laravel/boxes/homestead/versions/7.2.1/providers/virtualbox.box box: Download redirected to host: vagrantcloud-files-production.s3.amazonaws.com ==> box: Successfully added box 'laravel/homestead' (v7.2.1) for 'virtualbox'!Successfulluyとなったので、以下のコマンドでboxがちゃんと追加されたか確認する。
$ vagrant box list laravel/homestead (virtualbox, 7.2.1)Homesteadのファイルをダウンロード
ダウンロードしたファイルは任意のディレクトで良いが、今回はホームディレクトリの直下にダウンロードするので移動しておく。
$ cd $ pwd /Users/ユーザー名以下のコマンドでgithubからリポジトリをクローンします。
$ git clone https://github.com/laravel/homestead.git Homestead Cloning into 'Homestead'... remote: Enumerating objects: 59, done. remote: Counting objects: 100% (59/59), done. remote: Compressing objects: 100% (46/46), done. remote: Total 3606 (delta 37), reused 22 (delta 13), pack-reused 3547 Receiving objects: 100% (3606/3606), 782.03 KiB | 863.00 KiB/s, done. Resolving deltas: 100% (2190/2190), done.Homesteadの初期化
Homesteadは
Homestead.yaml
というファイルに色々書くことで、任意の設定をすることができる。
Homestead.yaml
は初期化することで生成される。ダウンロードしたHomesteadディレクトリに移動する。
中に色々なファイルが入っているが、初期化をしていないのでHomestead.yaml
が生成されていない。$ cd Homestead/ $ ls CHANGELOG.md Vagrantfile composer.lock phpunit.xml.dist scripts Homestead.yaml.example bin init.bat readme.md src LICENSE.txt composer.json init.sh resources tests以下のコマンドで初期化する。
$ bash init.sh Homestead initialized! $ ls CHANGELOG.md LICENSE.txt aliases composer.lock phpunit.xml.dist scripts Homestead.yaml Vagrantfile bin init.bat readme.md src Homestead.yaml.example after.sh composer.json init.sh resources tests
Homestead initialized!
と表示されたので、中を確認するとHomestead.yaml
が生成されている。SSH鍵のファイルの作成
ホストOSとゲストOSの通信はSSHで行うので、それに必要な鍵を作成する。
まずはホームディレクトリに移動して既に鍵があるかを確認。
$ cd $ ls -la | grep .ssh
id_rsa
とid_rsa.pub
が表示されれば、既にSSH鍵ファイルはあります。無い場合は以下のコマンドで作成する。
$ ssh-keygen -t rsa途中で
Enter file in which to save the key (/Users/ユーザー名/.ssh/id_rsa):
と保存するディレクトリを聞かれるので、そのままEnterを押す。次に
Enter passphrase
とパスフレーズを求められるので任意のパスフレーズを設定する。
Enter same passphrase again:
と再度確認を求められるので設定したパスフレーズを入力する。$ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/Users/ユーザー名/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/ユーザー名/.ssh/id_rsa. Your public key has been saved in /Users/ユーザー名/.ssh/id_rsa.pub. The key fingerprint is: ...(以下省略)...ファイルキーが作成されたので、先ほどのコマンドで確認する。
$ ls -la | grep .sshHomesteadの設定
Homestead(仮想マシン)の各設定は、初期化した時に生成した
homestead.yaml
に記述します。Homesteadのディレクトリに移動し、vimで
Homestead.yaml
を編集。$ cd Homestead/ $ vim Homestead.yaml下記が
Homestead.yaml
のデフォルトのです。Homestead.yaml--- ip: "192.168.10.10" # IPアドレス memory: 2048 # 割り当てるメモリ cpus: 2 # 割り当てるCPU provider: virtualbox # 使用するプロバイダ # SSH公開鍵のパス authorize: ~/.ssh/id_rsa.pub # SSH秘密鍵のパス keys: - ~/.ssh/id_rsa # 共有ディレクトリの設定 # map: ホストマシン側で共有したいディレクトリ # to: ゲストマシン側で共有したいディレクトリ folders: - map: ~/code to: /home/vagrant/cod # Nginxサイトの設定 # map: アクセスするドメイン名の設定 # to: ゲストマシンNginxのドキュメントルートの設定 sites: - map: homestead.test to: /home/vagrant/code/public # 使用するDB名 databases: - homestead # ports: # - send: 50000 # to: 5000 # - send: 7777 # to: 777 # protocol: udp # blackfire: # - id: foo # token: bar # client-id: foo # client-token: barrails serverは3000なので、コメントアウトを外して新しく記述する。
ports: - send: 3000 to: 3000それ以外はデフォルトのままで使用しますが、共有ディレクトリの設定や、ドキュメントルートの設定などの変更が可能です。
デフォルトの
folders
だとホストマシンのホームディレクトリ直下にcode
ディレクトリが指定されていますが、現在code
ディレクトリがないので作成しておきます。今後プロジェクトフォルダを作成したときは、この
code
ディレクトリのなかに入ることになります。$ mkdir ~/code【注意】
sites
プロパティをHomestead boxのプロビジョニング後に変更した場合、仮想マシンのNginx設定を更新するため、vagrant reload --provision
を再実行する必要があります。ホスト名の設定
IPアドレス
192.168.10.10
にhomestead.test
というドメイン名で設定してあり、hosts
ファイルに追記することで、Webブラウザでhttp://homestead.test
にアクセスすることができる。MacとLinuxでは
/etc/hosts
にファイルがあるので追加していく。
アクセス権がないと保存できないので、sudo
をつけて192.168.10.10 homestead.app
を追記する。$ sudo vim /etc/hosts ## # Host Database # # localhost is used to configure the loopback interface # when the system is booting. Do not change this entry. ## 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost 192.168.10.10 homestead.testVagrant Boxの実行
各種設定が終わったら仮想マシンを起動します。
Homesteadのディレクトリ内に
Vagrantfile
があるので、違うディレクトリにいる場合は移動します。$ pwd /Users/ユーザー名/Homestead下記のコマンドで仮想マシンを起動する。
$ vagrant up Bringing machine 'homestead-7' up with 'virtualbox' provider... ==> homestead-7: Importing base box 'laravel/homestead'... ==> homestead-7: Matching MAC address for NAT networking... ==> homestead-7: Checking if box 'laravel/homestead' version '7.2.1' is up to date... ==> homestead-7: Setting the name of the VM: homestead-7 ==> homestead-7: Clearing any previously set network interfaces... ...(以下省略)...以下のコマンドで起動できているかを確認。
$ vagrant status Current machine states: homestead-7 running (virtualbox) The VM is running. To stop this VM, you can run `vagrant halt` to shut it down forcefully, or you can run `vagrant suspend` to simply suspend the virtual machine. In either case, to restart it again, simply run `vagrant up`.
running (virtualbox)
となっているので起動できていることが確認できます。あとは
ssh
コマンドで仮想マシンに入ります。$ vagrant ssh Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-47-generic x86_64) Thanks for using _ _ _ | | | | | | | |__ ___ _ __ ___ ___ ___| |_ ___ __ _ __| | | '_ \ / _ \| '_ ` _ \ / _ \/ __| __/ _ \/ _` |/ _` | | | | | (_) | | | | | | __/\__ \ || __/ (_| | (_| | |_| |_|\___/|_| |_| |_|\___||___/\__\___|\__,_|\__,_| * Homestead 8.4.0 released! * Settler v7.2.1 released! Make sure you update 0 packages can be updated. 0 updates are security updates. Last login: Thu May 24 08:54:14 2019 from 10.0.2.2 vagrant@homestead:~$無事に仮想マシンに入れたので、あとはRailsの環境を構築していきます。
Ruby on Railsの環境を構築
Homesteadが立ち上がったのであとは必要なソフトなどを入れていくが、既にあるのがほとんどなので
rbenv
等を入れていく。必要なライブラリをインストール
Ubuntu自体をアップデート(最新化)します。
$ sudo apt update今回はMysqlを使用するので、必要な関連パッケージをインストール
$ sudo apt install mysql-server mysql-client $ sudo apt install libmysqlclient-devRubyインストール
rbenvをインストール
rbenvとは、Rubyの「インストール」と「バージョン切替」を簡単におこなえるようにするためのツールです。
Gitを使ってrbenvをインストールします
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv Cloning into '/home/vagrant/.rbenv'... remote: Enumerating objects: 15, done. remote: Counting objects: 100% (15/15), done. remote: Compressing objects: 100% (11/11), done. remote: Total 2759 (delta 4), reused 8 (delta 4), pack-reused 2744 Receiving objects: 100% (2759/2759), 528.92 KiB | 874.00 KiB/s, done. Resolving deltas: 100% (1724/1724), done.インストールできたらrbenvコマンドが使えるように設定する
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile $ exec $SHELL -lrbenvコマンドが使えるようになったか確認する
$ rbenv --version rbenv 1.1.2-2-g4e92322Rubyをビルドするために使うプラグインの「ruby-build」をインストール
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build Cloning into '/home/vagrant/.rbenv/plugins/ruby-build'... remote: Enumerating objects: 38, done. remote: Counting objects: 100% (38/38), done. remote: Compressing objects: 100% (25/25), done. remote: Total 9750 (delta 14), reused 29 (delta 8), pack-reused 9712 Receiving objects: 100% (9750/9750), 2.08 MiB | 2.10 MiB/s, done. Resolving deltas: 100% (6340/6340), done.インストールできるRubyのバージョンを確認する
$ rbenv install --list Available versions: 1.8.5-p52 1.8.5-p113 1.8.5-p114 1.8.5-p115 1.8.5-p231 1.8.6 1.8.6-p36 1.8.6-p110 1.8.6-p111 1.8.6-p114 ...(途中省略)... 2.5.4 2.5.5 2.6.0-dev 2.6.0-preview1 2.6.0-preview2 2.6.0-preview3 2.6.0-rc1 2.6.0-rc2 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0-dev jruby-1.5.6 jruby-1.6.3 ...(以下省略)...バージョンを指定してRubyをインストール。
終わったら仮想マシンにインストールされているRubyのバージョンを確認します。$ rbenv install -v 2.6.3 /tmp/ruby-build.20190524115043.18071 ~ Downloading ruby-2.6.3.tar.bz2... -> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.3.tar.bz2 Installing ruby-2.6.3... /tmp/ruby-build.20190524115043.18071/ruby-2.6.3 /tmp/ruby-build.20190524115043.18071 ~ checking for ruby... /home/vagrant/.rbenv/shims/ruby config.guess already exists config.sub already exists checking build system type... x86_64-pc-linux-gnu ...(以下省略)... $ rbenv versions * system (set by /home/vagrant/.rbenv/version) 2.6.3インストールされているのを確認できたら、仮想マシン全体で有効にするRubyのバージョンを指定する。
ディレクトリごとに指定する場合は
global
ではなく、有効にしたいディレクトリでlocal
を指定する。$ rbenv global 2.6.3 $ rbenv versions system * 2.6.3 (set by /home/vagrant/.rbenv/version)アスタリスクが移動しました。
ちゃんと最新バージョンがインストールされたかを確認$ ruby -v ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]bundlerのインストール
bundlerはプロジェクトごとで使うgemを、インストールしたり使用したりする。
どのバージョンが必要なのかは、プロジェクトフォルダにあるGemfile
を読み取りることで実行される。bundlerはRubyGemsからインストールできるので
gem
コマンドでインストールする。
bundle
コマンドが使えるようになるので確認する。$ gem install bundler Fetching bundler-2.0.1.gem Successfully installed bundler-2.0.1 Parsing documentation for bundler-2.0.1 Installing ri documentation for bundler-2.0.1 Done installing documentation for bundler after 2 seconds $ bundle -v Bundler version 2.0.1Ruby on Railsのインストール
Ruby on Railsは、
gem
コマンドでインストールする。
最新ではないバージョンを指定したい場合は-v
オプションをつけて指定。インストールが終わったら
rails
コマンドが使用できるので確認する。$ gem install rails Fetching i18n-1.6.0.gem Fetching rack-2.0.7.gem ...(途中省略)... Removing ruby (1:2.5.1) ... Removing ruby2.5 (2.5.1-1ubuntu1.2) ... Removing libruby2.5:amd64 (2.5.1-1ubuntu1.2) ... Removing rake (12.3.1-1) ... Removing ruby-test-unit (3.2.5-1) ... Processing triggers for libc-bin (2.27-3ubuntu1) ... Processing triggers for man-db (2.8.3-2ubuntu0.1) ... $ rails -v Rails 5.2.3Railsアプリを作成
Homestead.yaml
で共有ディレクトリの設定がすでにできているので、code
ディレクトリ内にファイルを作る。$ cd $ cd code/今回は例として
test_app
というプロジェクトファイルを作成します。プロジェクトファイルは
rails new
というコマンドで作成でき、-d
オプションで使用するデータベースを指定できます。オプションを指定しない場合は「SQLite」というデータベースがデフォルトで使用されますが、今回は「Mysql」を使用するので以下のコマンドでファイルを作成します。
$ rails new test_app -d mysql create create README.md create Rakefile create .ruby-version create config.ru create .gitignore create Gemfile run git init from "." Initialized empty Git repository in /home/vagrant/code/test_app/.git/ create package.json create app ...(途中省略)... Using spring-watcher-listen 2.0.1 Using turbolinks-source 5.2.0 Using turbolinks 5.2.0 Using uglifier 4.1.20 Using web-console 3.7.0 Bundle complete! 18 Gemfile dependencies, 78 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. run bundle exec spring binstub --all * bin/rake: spring inserted * bin/rails: spring inserted $ ls test_app確認すると
test_app
というファイルが作成されています。Railsサーバーの起動
Railsサーバーはプロジェクトフォルダに移動して
rails s
コマンドで起動できます。しかしVagrant環境で仮想マシンを起動している場合は、
rails s -b 0.0.0.0
というコマンドでRailsサーバーを起動させます。localホスト以外からはアクセスできないため、
-b 0.0.0.0
オプションをつけて全てのIPアドレスを許可します。$ cd test_app/ $ rails s -b 0.0.0.0 => Booting Puma => Rails 5.2.3 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stopこのように表示されれば無事にサーバーが起動したことになります。
サーバーを停止したいときはCtrl + c
で停止することできます。ブラウザでアクセス
ドメイン名の設定もデフォルトでしてあるので、ブラウザには
homestead.test:3000
でアクセスできます。Mysqlのパスワードを設定
これはRailsアプリがMysqlにhomesteadユーザーでログインしようとした結果、「パスワードがない」と言われていることが原因です。
- データベース : homestead
- ユーザー名 : homestead
- パスワード : secret
Homesteadでは上記のようなデフォルトの設定があるので、データベースの設定ファイルにパスワードを記述します。
【注意】
サーバーを起動しているタブではコマンド操作ができないので、新たにタブを増やし再度ssh
で仮想マシンに接続します。データベースの設定ファイルは
config
ディレクトリの中にdatabase.yml
という名前で入っています。$ ls config/ application.rb credentials.yml.enc environments master.key spring.rb boot.rb database.yml initializers puma.rb storage.yml cable.yml environment.rb locales routes.rb $ vim config/database.ymlvimで開き
password
の所にsecret
を追記して保存します。database.yml# MySQL. Versions 5.1.10 and up are supported. # # Install the MySQL driver # gem install mysql2 # # Ensure the MySQL gem is defined in your Gemfile # gem 'mysql2' # # And be sure to use new-style password hashing: # https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html # default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: secret socket: /var/run/mysqld/mysqld.sock development: <<: *default database: test_app_development # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: test_app_test保存したらサーバーを一度停止し、再起動してからブラウザにアクセスしてみます。
データベース作成
再びアクセスしてみると先ほどのエラーはなくなりましたが、違うエラーが出てきました。
これは
Unknown database 'test_app_development'
とでていて、test_app_development
というデータベースが無いよと注意されています。Railsには
rails db:create
というコマンド存在していて、database.yml
に記述されている情報を元に、データベースを作成してくれます。これらはデフォルトで
プロジェクトフォルダ名_development
プロジェクトフォルダ名_test
という2つのデータベース名が記述されているので特に変更はせずに、先ほどの
rails db:create
コマンドを実行して上記の2つのデータベースを作成します。$ rails db:create Created database 'test_app_development' Created database 'test_app_test'データベースが作成されたので再度ブラウザにアクセスしてみます。
上記のように表示されれば成功です。これで問題なくアクセスができました。
あとはアプリケーション制作
これでHomesteadを使用したRuby on Railsの環境を構築できました。
あとは作成したフォルダでアプリケーションを作ってみてください。参考記事
- 投稿日:2019-05-24T22:07:32+09:00
Railsで基本情報技術者試験の過去問題サイトを作る(3:親子関係、登録編)
はじめに
ゆる〜く学ぶ。みんなのWeb勉強コミュニティー。 「にゅ〜ぶる会」を運用中です。
https://newburu.github.io/そこで、何か教育用のコンテンツが欲しいなぁ〜と思い立ち、今回の企画をスタートしました!
Railsで基本情報技術者試験の過去問題サイトを作ります!
最終目標
- 問題・回答の登録は、Scaffoldで簡易でOK
- APIを用意して、ランダムに問題を抽出する機能を追加する
- TwitterBOT、LINEBOT、SlackBOTが出来たら良いな
履歴
1:構築編
https://qiita.com/newburu/items/ed59f47ac645b19620f6
2:日本語化(i18n)編
https://qiita.com/newburu/items/4f12fdb61bf6cd601545/
3:親子関係、登録編
本ページ今回やる事
- 親子関係を設定し、登録しやすくする
※レイアウトをやろうと思いましたが、こちらの方が優先なので、予定を変更させて頂きました。
親子関係を設定し、登録しやすくする
1. gem 'cocoon', gem 'jquery-rails'を追加します。
Rails5.1からjQueryがなくなったため、jquery-railsも追加する必要があります。
./Gemfilegem 'cocoon' gem 'jquery-rails'bundle-install$ bundle install2. Modelに、親子関係を設定します。
・親(question)には、「has_many :answers」を追加します。
・親が削除された時に、一緒に子も削除されるように、「dependent: :destroy」をつけます。
・accepts_nested_attributes_forを使うと、親と子を一緒にcreate/update出来るようにします。
・削除できるよう「allow_destroy: true」をつけます。app/models/question.rbhas_many :answers, dependent: :destroy accepts_nested_attributes_for :answers, allow_destroy: true子(models/answer.rb)は、作成時にreferencesとしているため、自動で設定されています。
app/models/answers.rbbelongs_to :question3. Viewに、親子同時に登録・更新出来るように設定します。
application.jsに以下を追加します。
app/javascripts/application.js//= require jquery //= require cocoonviewを変更します。
app/views/questions/_form.html.slim.answers = f.fields_for :answers do |answer| = render 'answer_fields', f: answer = link_to_add_association "追加", f, :answers子供の情報登録用フォームになるviewファイルを新規作成します。
app/views/questions/_answer_fields.html.slim.nested-fields .field = f.label :msg = f.text_area :msg .field = f.label :correct = f.check_box :correct = link_to_remove_association "削除", f4. コントローラーで登録可能にします。
以下のように子のパラメータを受け取れるようにしましょう。
app/controllers/questions_controller.rbdef question_params params.require(:question).permit(:category1, :category2, :category3, :msg, answers_attributes: [:id, :msg, :correct, :_destroy]) end5. 確認します。
「追加」ボタンを押すたびに、子供の入力エリアが追加されます。
今回はここまで
ありがとうございました!
次回は、参照画面などに、子供の情報を追加していこう。
- 投稿日:2019-05-24T21:53:15+09:00
Ajaxによる非同期通信で、データベースに二重に投稿される問題
問題
form_forでデータを受け取り、Ajaxによる非同期通信でHTMLを差し替えて投稿する際、
投稿内容がデータベースに二重に投稿される問題が起きる。原因
form_forにて投稿する際、controllerのcreateアクションとformのPOST、両方がデータベースに投稿してしまう。
※このへん理解浅いので、理解が間違っていたらご指摘いただけると助かります。
解決法
jsファイルの非同期通信の関数の最後に return false; を記述しておく。
formのPOSTがキャンセルされるため、二重投稿されなくなる。補足
かなりピンポイントな問題と対策な気がしますが、検索しても解決方法が出てこなかったので記載しておきます。
なんとなくの理解ですので、言い方や理解の仕方が間違ってる可能性あります。
- 投稿日:2019-05-24T21:36:43+09:00
Heroku 本番環境 [Mysql2::Error::ConnectionError: Access denied for user root@localhost (using password: NO)] エラー
注)本ページに出てくる、usernameや、password、host値は存在しない値です。
問題:
rails c production で User.createをしようとしたら、
Mysql2::Error::ConnectionError: Access denied for user 'b2a63e570bc849'@'52.204.50.03' (using password: NO)
と言われてしまいました。ターミナル$ heroku config #mysql周辺情報をチェックします。 # 注意 'mysql2://[ユーザ名]:[パスワード]@[ホスト名]/[スキーマ名]?reconnect=true' です。 CLEARDB_DATABASE_URL: mysql://kefbwfffn349:31w1dd54@us-cdbr-iron-east-04.cleardb.net/heroku_sufbd6sjdhsb5d?reconnect=true DATABASE_PASSWORD: 31w1dd54 DATABASE_URL: mysql2://kefbwfffn349:31w1dd54@us-cdbr-iron-east-04.cleardb.net/heroku_sufbd6sjdhsb5d?reconnect=true $ mysql -u kefbwfffn349 -h us-cdbr-iron-east-04.cleardb.net -p #mysqlに入れるか? Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is ... Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> exit # 入れました。host,username,passwordはあってるのに、、、
となると,検証
原因はエラー文の中のusing password: NOかな???と思い、
config/database.ymldefault: &default adapter: mysql2 encoding: utf8 username: root password: pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 production: <<: *default database: heroku_sufbd6sjdhsb5d username: kefbwfffn349 host: us-cdbr-iron-east-04.cleardb.net password: <%= ENV['DATABASE_PASSWORD'] %> socket: /var/lib/mysql/mysql.sockの
password: <%= ENV['DATABASE_PASSWORD'] %>
の部分を直接パスワードに置き換えることにしました。config/database.ymldefault: &default adapter: mysql2 encoding: utf8 username: root password: pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 production: <<: *default database: heroku_sufbd6sjdhsb5d username: kefbwfffn349 host: us-cdbr-iron-east-04.cleardb.net password: 31w1dd54 socket: /var/lib/mysql/mysql.sockそしたら、mysqlに繋がった、、、とりあえずは助かった、、、
本来なら、password: <%= ENV['DATABASE_PASSWORD'] %>
でheroku:configのDATABASE_PASSWORD(キー)が読み込まれて、31w1dd54(値)が取得されるはずなのに、、、
色々調べてみます。注)本ページに出てくる、usernameや、password、host値は存在しない値です。
- 投稿日:2019-05-24T20:02:12+09:00
bundle installで /vender/bundle にgemインストールされるのを解除する
問題
bundle installすると、/vender/bundle にgemインストールされてしまう。
デフォルトの場所にインストールされるように戻したい。原因
bundle installをいつの間にかパス指定して実行してしまっていた。
ターミナル$ bundle install --path vendor/bundle一度パス指定して実行すると、以降はパス指定しなくても、
そのパスがデフォルトのインストール先になってしまう?パス変更方法
ホームディレクトリの
/.bundle/config のファイルにパスが書いてあるので、
それを変更 or 削除する。
削除すると何もパス指定されてない状態に戻る。/.bundle/configBUNDLE_PATH: "vendor/bundle".bundle は隠しファイルなので、
shift + command + .(ドット)
のコマンドで隠しファイルの表示/非表示を切り替えられる。参考
以下の状況で問題が発生:
最初はデフォルト状態でbundle installしていたプロジェクトで、
途中からbundle installするといつの間にか/vender/bundleにインストールされるようになってしまっていた。
GithubDesktopでコミットしようとすると以下エラーが出てコミットできない状態になった。
パス指定を削除することにより解決。
最初からパス指定してプロジェクトを立ち上げている場合は問題ないと思われる。GithubDesktopcommit failed - exit code 1 received github
- 投稿日:2019-05-24T18:25:53+09:00
rubocop について
rubocop とは
コーディング規約に準拠してるかチェックするgemです!
簡単に言いますと、インデントやメソッド名、改行などのチェックをしてくれるものです。
今回は、このrubocopさんについてを会社のプロダクトにいれた時(後入れ)のやり方にも交えて書きます。流れ
$gem install rubocop
or Gemfile にかいて$bundle install
$rubocop
で実行(たぶんめちゃめちゃ怒られます。。。)- 怒られた箇所修正!!!
.rubocop.yml を作成!
プロジェクト直下に「.rubocop.yml」を作成!!
rubocop.ymlRails: Enabled: true AllCops: Exclude: - 'db/schema.rb' - 'vendor/**/*' # 日本語でのコメントを許可 AsciiComments: Enabled: falseこのようなrubocop に対して制御するファイルを作成することで、ひとつひとつを修正していける!
(実際はその人や会社によってもっと設定がいることもあります。)と簡単そうですが、後入れの場合、まぁーめちゃくちゃ怒られます!
そこで、私が実際にやったやり方は、
$bundle exec rubocop -R --auto-gen-config --exclude-limit 999999オプションつけてrubocop コマンドを叩くと、「.rubocop_todo.yml」が作られます!!
このファイルは、現コードに対する一時回避の設定をしてくれます!これによって、怒られなくなります。
つまり、このファイルがなくてもエラーが起きないことがゴールです〜このファイルをもとに「.rubocop.yml」を設定していきます!
実際の流れ
0.はじめに
rubocop_todo.ymlinherit_from: .rubocop.ymlこれを記述して、両方のファイルが参照されるようにしておく!
or
.rucocop_todo.yml
の内容を.rubocop.yml
に全コピー
copを一個コメントアウト(削除)
$rubocop
何で怒られているかチェック
-> 直したくない(これからもこのcop に対しては無視したい)->3A
-> 直さないといけない->3B3A.
例: AsciiCommentsに関しては、全ページで許可したい!rubocop.yml# cop でどのようなことが怒られたかとか、どうしたいかを明記しておいたほうが良さそう AsciiComments: Enabled: falseを追記
もしくは、rubocop.ymlAsciiComments: Exclude: - 'app/models/hoge.rb'
3B.
$rubocop -a
自動で修正してくれる(これがめちゃ便利)単純にコードミスや可読性の高いコードにも直してくれる!
注:メソッドの名前などは直してくれない!!ちなみに、私は
$rubocop
回した段階で何で怒られているのかがわからないやつは、とりあえず$rubocop -a
を叩きました。
それで、diff でみてどう変更されたかを確認してました!!流れに関してはこんなもんです!ひたすら、怒られているところの確認-> 修正 or 「cop」 無視
.rubocop.ymlの書き方について
rubocopの対象から除外するファイル指定
rubocop.ymlAllCops: Exclude: - db/schema.rb - 'vendor/**/*'copの無効化・有効化
rubocop.ymlRails: Enabled: truerubocop.ymlLambda: Enabled: false基本的には、cop は有効化されているので
Enabled: true
はそこまで使わないかも
Enabled: false
はcop 自体を無視したいときに使います!warning のみを検知
rubocop.ymlBundler/OrderedGems: Severity: warningwarning レベルから取得
自動化
rubocop を最大活用するためには、自動化するしかないと思いまして、CircleCIと連携することにしました!
CircleCIに関しては、ここでは飛ばします笑circleci/config.ymljobs: build: steps: - run: bundle exec rubocopと設定するだけです!
自動で回してくれます!
github と CircleCI の連携と CircleCI ファイルの中にrubocop のコマンドを設定することでgithub 上で結果を確認できます!まとめ
そもそも導入した経緯-> レビューする人が忙しい!!細かい修正で何度もやり取りするのが無駄!
- チームみんなのコードで既存のcop の設定とは違う物が来たときなどのメンテナンスは、必要かなと。
- 私自身、プロダクトに導入したばかりで結果がまだわかっていないので、便利なのか、あまり良くないかはまだわかっていません(今更ですが)
非常に良かった点
- まだ、コードを書き始めて長くない私にとって、
$rubocop -a
によって修正されたり、怒られたりすることでコードの違った書き方を勉強するいい機会になった!(実際これが一番でかい)参考
https://qiita.com/kyohei_shimada/items/e739dec967eb5e61721c
https://blog-ja.sideci.com/entry/2015/03/12/160441
- 投稿日:2019-05-24T17:44:32+09:00
よく見かけるattr_accessorについて
はじめてのqiita投稿ですが、がんばりたいと思います。
1.ゲッターとセッターについて
railsを学び始めて早4ヶ月。
ただrailsのコードを書くだけではなく、rubyの基礎を学ぼうと思いました。
そこで気になったのがattr_accessor、これはよく見かけるがなんだろうと。さて本題。
ゲッターくんとセッターくんの役割を実際のコードで見てみようと思います。
class Book def book=(book) @book = book end def book @book end end1つ目のメソッドは引数で受け取ったデータをインスタンス変数に代入します。
このインスタンス変数を代入するためのメソッドのことを「セッター」と呼びます。2つ目のメソッドではbookメソッドの中身を変更して、設定した名前を返しています。
このインスタンス変数の内容を参照するためのメソッドを「ゲッター」と呼びます。2.attr_accessorについて
先程のゲッターとセッターを毎回毎回設定するのは大変ですよね
しかしrubyには便利なメソッドがあります。
それがattr_accessorという訳です。class Book attr_accessor :book endというようにすっきり書けたのでは無いでしょうか。
attr_accessorメソッドを用いると、シンボルで :book と書けるようになりました。3.さいごに
ちなみに、ゲッターのみの場合はattr_reader,セッターのみの場合はattr_writerと書けます。
という風に、普段何気なく使っているメソッドでもこれだけの仕事をしていたとは驚きですね。
これからも気になったら、どんどんqiitaでアウトプットしようと思います。
- 投稿日:2019-05-24T17:03:29+09:00
Railsコマンド
はじめに
備忘録としてまとめていきます。
随時追記していきます。Railsプロジェクトを作成
$ rails new [プロジェクト名] -d [データベース名]データベース作成
$ rails db:createサーバー起動
$ rails serverコントローラー作成
$ rails generate controller [コントローラー名(複数形)][アクション名・アクション名..][オプション名]モデル作成
$ rails generate model [モデル名(単数形)][属性名:データ型][属性名:データ型]マイグレーション
$ rails db:migrateテスト検証
$ rails testintegration作成
$ rails g integration_test [テストファイル名]統合テスト検証
$ rails test integrationindexを追加
$ rails g migration add_index_to_[テーブル名]_[カラム名] $ rails db:migrate #最後にDBをマイグレートする。カラム追加
$ rails g migration add_[カラム名]_to_[テーブル名] [カラム名][データ型]DBリセット
$ rails db:migrate:reset
- 投稿日:2019-05-24T16:59:27+09:00
RailsとLivedoor Weather Web Serviceを使って各地点の天気を表示させてみた
はじめに
前回の記事で、Rubyから天気情報を取得することができました。
RubyでWeather Hacksを使って天気を取得してみた
この取得処理とRailsを使って、Web上に天気情報を表示します。環境
macOS Mojave(10.14.4)
Ruby 2.6.3
Rails 5.2.3取得処理
以下の順で各地点の天気情報を取得します。
- 表示させたい地点名を読み込む
- Livedoor Weather Web Serviceで提供されている都市と1を使って、地点のidを取り出す
- 各地点の天気情報を取得する
表示させたい地点名を読み込む
テレビの全国天気で、よく表示される各地点のjsonファイルを作成します。
main_city.json{ "city":[ { "name": "釧路" }, { "name": "旭川" }, ・・・略・・・ { "name": "那覇" }, { "name": "石垣島" }これを読み込む処理を作成します。
def read file_path = File.expand_path('config/main_city.json', __dir__) @city_list = [] File.open(file_path, 'r') do |text| @parse_text = JSON.parse(text.read) @parse_text['city'].each { |city| @city_list.push(city['name']) } end endLivedoor Weather Web Serviceで提供されている都市と1を使って、各地点のidを取り出す
提供されている地点のIDをjsonファイルで書いておきます。
location_id.json{ "area": [ { "name": "北海道", "prefs":[ { "name": "北海道", "city":[ { "name": "稚内", "id": "011000" }, { "name": "旭川", "id": "012010" }, ・・・略・・・ { "name": "石垣島", "id": "474010" }, { "name": "与那国島", "id": "474020" } ] } ] } ] }このjsonファイルを読みこみ、各地点のidを取得します。
単純にforループで回すだけですね。
もっといい方法がありそうですが、今はこの方法でやります。def read_main_location_id parse_text = read_location_id location_list = LocationList.new reader = MainCityReader.new reader.read area = parse_text['area'] for area_no in 0..area.count - 1 prefs = area[area_no]['prefs'] for pref_no in 0..prefs.count - 1 city = prefs[pref_no]['city'] for city_no in 0..city.count - 1 city_name = city[city_no]['name'] if reader.contain?(city_name) location = Location.new location.area_name = area[area_no]['name'] location.pref_name = prefs[pref_no]['name'] location.location_name = city_name location.id = city[city_no]['id'] location_list.add(location) end end end end return location_list end private def read_location_id file_path = File.expand_path('config/location_id.json', __dir__) parse_text = '' File.open(file_path, 'r') do |text| parse_text = JSON.parse(text.read) end return parse_text end各地点の天気情報を取得する
idを使ってURLを作成します。
BASE_URL = 'http://weather.livedoor.com/forecast/webservice/json/v1?city='.freeze def create(location_id) return BASE_URL + location_id.to_s endURLから天気情報を持ったjsonファイルを取得します。
def read(url) response = URI.open(url) @parse_text = JSON.parse(response.read) endあとはjson内から天気情報を取得して終わりです。
表示結果
5/24は全国的に晴れのようですね。
今後
次にやっていきたいのは以下の3つですね。
- 最高気温、最低気温が取得できるようなので画面に追加
- 各地点をクリックすると、その地方の詳細な天気が見れるようにする
- ログイン機能を持たせ、ログインすると明日、明後日の天気も表示できるようにする
独学でRubyを書いているので、指摘事項ありましたらコメントに記載をお願いします。
- 投稿日:2019-05-24T16:06:04+09:00
Ruby on rails: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)となった時の対処法
- 投稿日:2019-05-24T15:42:56+09:00
RailsアプリケーションのRubyとBundlerのバージョンをアップデートする
環境
$ cat /etc/system-release Amazon Linux AMI release 2018.03 # プロジェクトのディレクトリ内にて $ rbenv -v rbenv 1.1.1-30-gc8ba27f $ rbenv versions * 2.5.3 (set by /var/www/myapp/.ruby-version) $ bundle -v Bundler version 1.17.1 # すでにアップデートされたRailsアプリケーション $ bin/rails -v Rails 5.2.3はじめに
Railsアプリケーションをアップデートするにあたり、RubyとBundlerのバージョンもアップデートする必要があったので備忘録として残しておきます。
Rubyのバージョンを確認
rbenvにインストールしたいバージョンが存在するか確認します。
$ rbenv install -l以上のコマンドで該当バージョンが出てこなければ、ruby-buildをアップデートする必要があります。
$ cd /usr/local/rbenv/plugins/ruby-build && git pull && cd -アップデートしたいRubyのインストール
# ruby2.6.2をインストール $ rbenv install 2.6.2 # ruby2.6.2に切り替え $ rbenv global 2.6.2 # 再読み込みし、切り替えの反映 $ rbenv rehash # 反映されたことを確認 $ rbenv versions system 2.5.3 * 2.6.2 (set by /var/www/myapp/.ruby-version)トラブルシューティング
インストールしたRubyのバージョンに切り替わらない
# 正しい参照先 $ which ruby /usr/local/rbenv/shims/rubyrbenvでRubyをインストールした場合、以上のように正しい参照先は
/.rbenv/shims/ruby
となります。それ以外だと参照先が間違っている可能性があるため、PATHを通します。EC2では全てのユーザーがログイン時にrbenvを使えるようにするために、/etc/profile.d/rbenv.sh
にPATHを通している場合があります。# /etc/profile.d/rbenv.shにPATHの記載があるかもしれない場合は要確認! $ cat /etc/profile.d/rbenv.sh export RBENV_ROOT="/usr/local/rbenv" export PATH="${RBENV_ROOT}/bin:${PATH}" eval "$(rbenv init -)" # 以上にPATHがなければ、~/.bash_profileに記述 $ echo 'export PATH=~/.rbenv/bin:$PATH' >> ~/.bash_profile $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile # ~/.bash_profileの変更を反映 $ source ~/.bash_profileRubyのバージョンをアップデートしたものの、bundle installができない
Gemfile.lock
に記載されているBUNDLE_WITHとbundlerのバージョンが異なる場合、bundle install
をした時に以下のようなエラーが出てしまいます。$ bundle install --path vendor/bundle -j4 ・・・ find_spec_for_exe': can't find gem bundler (>= 0.a) (Gem::GemNotFoundException) ・・・まずは
Gemfile.lock
にて、BUNDLED WITH
を確認します。確認したバージョンのbundlerをインストールします。
bundlerの参照先を確認し、/.rbenv/shims/bundler
となっていればOKです。# BUNDLE_WITHでbundlerのバージョンを確認 $ vim Gemfile.lock ・・・ BUNDLED WITH 1.17.3 # Gemfile.lockに記載されているバージョンと同じbundlerを指定してインストール $ rbenv exec gem install bundler -v 1.17.3 # 参照先の確認 $ which bundler /usr/local/rbenv/shims/bundler
- 投稿日:2019-05-24T15:26:09+09:00
jsフレームワークstimulusでyahoo地図を操作してみた(その二)
前回の続き。
今回参照したサイトはこちら……というか今回は、このサイトの記述を
【stimulus使用+「googlemap→yahoo地図」】
に書き換えただけです。やったこと、やる予定のこと
0. railsアプリへのstimulus適用(以前書いた記事)
1. stimulusでyahoo地図を表示(前回)
2. 表示した地図上で、指定座標に移動(←今ここ)
……今回は「指定座標」はコードに直接書いています。
座標取得の方法は「3」以降でやる予定。3. 住所から座標を取得し、それをDBに登録(こんど書く)
4. 「3」で登録した情報を選択し、yahoo地図上でその場所に移動(そのうち書く)
ということで、前回作成した地図上を移動できるようにしてみます。
まずは、移動用のボタンを用意する。
app/views/users/test.html.erb<div data-controller="moving"> <!-- 中略 --> <input type="button" id="tokyo" value="東京" data-action="click->moving#tokyo"> <input type="button" id="shinbashi" value="新橋" data-action="click->moving#sinbasi"> <input type="button" id="shinagawa" value="品川" data-action="click->moving#sinagawa"> </div>ボタンを三個用意して、それぞれにデータアクション名を設定……「data-action="click->moving#tokyo"」(又は「sinbasi」「sinagawa」)
なおこの記述は、【「data-controller="moving"」であるdiv要素内で行う。このアクション名設定により、ボタンクリックするとstimulusのアクションが発生するので……
アクション発生時の処理をstimulus用jsファイルに記述
……前回作成したjsファイルに、
app/javascript/controllers/moving_controller.jsinitialize() { this.map = new Y.Map(this.mapTarget.id); this.map.drawMap(new Y.LatLng(35.66572, 139.73100), 17, Y.LayerSetId.NORMAL); var center = new Y.CenterMarkControl var control = new Y.LayerSetControl(); this.map.addControl(center); this.map.addControl(control); } //↓追記 tokyo() { this.map.panTo(new Y.LatLng(35.680865,139.767036), true); } sinbasi() { this.map.panTo(new Y.LatLng(35.666397,139.758153), true); } sinagawa() { this.map.panTo(new Y.LatLng(35.629867,139.74015), true); } //↑追記「35.680865,139.767036」などの座標は、参照サイトから写してきた実際の位置情報です。
「東京」「新橋」「品川」のボタンが追加され、クリックするとそれぞれの場所に移動できるようになりました。
とりあえずは今回はここまで、続きは次回以降(の、予定)。
- 投稿日:2019-05-24T15:03:18+09:00
Rails minitest のアサーション
はじめに
備忘録としてまとめていきます。
随時追記していきます。assert_select #アクション実行の結果として描写されるHTMLの内容の検証assert_template #そのアクションで指定されたテンプレートが描写されているかの検証assert #真であることを主張するassert_not #偽であることを主張するassert_no_difference #式で評価した結果の数値は、ブロックで渡されたものを呼び出す前と後で違いがないと主張する assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } endfollow_redirect! #リクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end follow_redirect!参考リンク・資料
- 投稿日:2019-05-24T15:03:18+09:00
Rails minitest コマンド
はじめに
備忘録としてまとめていきます。
随時追記していきます。assert_select #アクション実行の結果として描写されるHTMLの内容の検証assert_template #そのアクションで指定されたテンプレートが描写されているかの検証assert #testはtrueであると主張するassert_not #testはfalseであると主張するassert_no_difference #式で評価した結果の数値は、ブロックで渡されたものを呼び出す前と後で違いがないと主張する assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } endfollow_redirect! #リクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end follow_redirect!参考リンク・資料
- 投稿日:2019-05-24T14:43:27+09:00
form_withでvalidationエラーが出ない原因と対処法
はじめに
Rails5.1からform_forとform_tag非推奨になり、form_withが推奨になりました。
form_forの感覚でform_withを使ってハマったのでまとめていきます。問題: validationエラーがviewに表示されない
下記のようにコーディングしており、
validationエラーがnewのviewに発生するはずなのにエラーメッセージが発生しない。new.html.erb<% provide(:title, "Sign up")%> <h1>Sign up</h1> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with model: @user do |f| %> <%= render 'shared/errors_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form_control' %> <%= f.label :email %> <%= f.email_field :email, class: "form_control" %> <%= f.label :password %> <%= f.password_field :password, class: 'form_control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form_control' %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div> </div>_errors_messages.html.erb<% if @user.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@user.errors.count, "error") %>. </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>原因: form_withはデフォルトオプションがremote:trueになっている
Ajaxを行うときに、form_tagやform_forはオプションで
remote: true
で対応していたが、
form_withではデフォルトでremote: trueになっている(デフォルトでAjaxになっている)。解決: form_withのオプションをlocal: trueを指定する
formでAjaxを使用しない時はオプション
local: true
を指定する。
form_withのオプションでlocal: true
を指定するとvalidationエラーをviewに表示できる。参考リンク・資料
Rails 5.1のform_withでViewにvalidationエラー表示
ActionView::Helpers::FormHelper
actionview/lib/action_view/helpers/form_helper.rb
- 投稿日:2019-05-24T14:13:51+09:00
js.erbファイルにコメントを書くときは//を使わない
修正前
index.js.erb// コメントが見えてるよ! $("#ajax_panel").html("<%= escape_javascript(render partial: "ajax_panel") %>");結果
ツールを使ってファイルを見ると...
(画像はChromeのデベロッパーツールのNetworkパネルから確認)
コメントに重要な内容が含まれていると、敵の攻撃を手助けしてしまう可能性があり危険です。
修正
erbファイルのコメントの書き方に修正します。
index.js.erb<%# コメントが見えないだと... %> $("#ajax_panel").html("<%= escape_javascript(render partial: "ajax_panel") %>");修正後
- 投稿日:2019-05-24T14:00:19+09:00
Railsアプリ作成手順まとめ
はじめに
備忘録としてまとめていきます。
随時追記していきます。Railsプロジェクトを作成
$ rails new <プロジェクト名> -d <データベース>ローカルリポジトリを作成、リモートリポジトリにプッシュ
$ git init $ git add $ git commit -m "Initial commit" $ git remote add origin https://github.com.ユーザー名.リポジトリ名 $ git push -u origin masterデータベース作成
$ rails db:createHerokuへデプロイ
$ heroku create $ git push heroku master
- 投稿日:2019-05-24T13:53:15+09:00
RailsのCI環境でChildProcess::Errorが発生する場合の対処法
結構長いこと悩まされていたエラーだったのですが、原因がわかりましたので共有します。
CircleCIが安定しない
テストは全て通っているのですが、時々というか結構な頻度で以下のようなエラーが出てCIがコケていました。
/home/circleci/project_name/vendor/bundle/ruby/2.6.0/gems/childprocess-1.0.1/lib/childprocess/abstract_process.rb:188:in
assert_started': process not started (ChildProcess::Error)
block in exit_hook'
6: from /home/circleci/project_name/vendor/bundle/ruby/2.6.0/gems/selenium-webdriver-3.141.5926/lib/selenium/webdriver/common/platform.rb:150:in
5: from /home/circleci/project_name/vendor/bundle/ruby/2.6.0/gems/selenium-webdriver-3.141.5926/lib/selenium/webdriver/common/service.rb:110:instop'
ensure in stop'
4: from /home/circleci/project_name/vendor/bundle/ruby/2.6.0/gems/selenium-webdriver-3.141.5926/lib/selenium/webdriver/common/service.rb:110:in
3: from /home/circleci/project_name/vendor/bundle/ruby/2.6.0/gems/selenium-webdriver-3.141.5926/lib/selenium/webdriver/common/service.rb:163:instop_process'
process_exited?'
2: from /home/circleci/project_name/vendor/bundle/ruby/2.6.0/gems/selenium-webdriver-3.141.5926/lib/selenium/webdriver/common/service.rb:180:in
1: from /home/circleci/project_name/vendor/bundle/ruby/2.6.0/gems/childprocess-1.0.1/lib/childprocess/unix/process.rb:31:inexited?'
assert_started': process not started (ChildProcess::Error)
/home/circleci/project_name/vendor/bundle/ruby/2.6.0/gems/childprocess-1.0.1/lib/childprocess/abstract_process.rb:188:inこのエラーメッセージ等でググっていたのですが、解決方法が見当たらず…。時々失敗するけれど時々成功するため、ときどき調査しようとしては諦めていました。
他のエラーメッセージに気づく
弊社の他のプロジェクトでも同様の症状が出ており、同僚が調査していたのですが、そのときに見つけたメッセージがこれ。
Text file busy - /home/circleci/.webdrivers/chromedriver
私はエラーメッセージのほうばかりを見ていて気づいてなかったのですが、RSpecが落ちたテストをリトライするところに出ていました。
こちらでググると、Rails 6.0系のissueとPRがヒットしました。
https://github.com/rails/rails/pull/36292
Rails 6系ではデフォルトで並列テストをサポートするという認識ですが、弊社のCIもparallel_testsでテストを回していて、同じ症状のようです。
症状の詳細は、webdriversがchromedriverのアップデートをしようとするが、並列でそれが行われてしまい、片方のプロセスが起動できなかったということです。
そこで、並列テストが実行される前にchromedriverのアップデートをしておけば、この問題は発生しなくなると考えました。(上記のPRもそういうことをやっていますが)
修正方法
並列テストが実行される前にchromedriverを更新するよう.circleci/config.ymlを修正しました。parallel_tests等でテストを起動するとその時点で並列化されているから、その前にやっておきます。
.circleci/config.ymlsteps: # 略。ただし、DB作成後でないとrails runnerが失敗するので注意。 - run: name: Update chromedriver command: env RAILS_ENV=test bin/rails runner "Webdrivers::Chromedriver.update" # 略。テストを実行結果
10回連続で同じテストを実行しましたが、全部成功しました
もし並列テストが不安定だ〜という方はこれを追加してみましょう!
- 投稿日:2019-05-24T13:25:13+09:00
RubyのJITコンパイラを理解したい!rubykaigiの香りを添えて
まえがき (ポエムなので読み飛ばせます)
「Rubyは遅い!」と言われがちですが、来たる Ruby3.0 に向けて、今までよりも3倍の高速化を図る試み、
Ruby 3x3
が進行しています。
先月行われたrubykaigi2019でもその取り組みが発表され、多くの期待の眼差しが向けられていました。とりわけ、高速化の肝になるであろう
JITコンパイラ
はセッションだけでなくキーノートでも触れられ、大きな注目を集めていました。しかし、電子計算機の仕組みを独学で(しかも基本情報技術者試験のため)ふわっと勉強した筆者(非情報系卒)にとっては、いささか難しい内容で、オープンされるスライドのたびに変わる会場の空気を「完全に理解した」とは口が避けても言えませんでした。
それでも、
JITコンパイラ
をはじめとする数々の試みが、Rubyエンジニアにとって、とても興味深く、同時に楽しいことであることは十分に伝わってきました
今回はrubykaigi2019の残り香を楽しみつつ、多くのセッションでも取り上げたれたJITコンパイラについて、詳しく見ていきたいと思います。この記事では、JITの概念をふわっと理解するために、ふわっと理解しようとしている筆者が書いています。
もし、誤りや怪しい部分がございましたら、忌憚ないご意見をいただければ幸いです! (>_<)JITコンパイラ is なに?
JITコンパイラ
は Ruby2.6 からオプションで追加された、Rubyを高速に実行しようとする仕組みです。
JIT(Just-In-Time Compiler)とは、コードがまさに実行されるそのときにコンパイルされる仕組みのことで、RubyだけでなくJavaの実行環境でも取り入れられている仕組みです。JITコンパイルという用語は、ソフトウェアを構成するモジュールやクラス、関数などの、ある単位のコードがまさに実行されるその時に、コンパイルされることから「Just In Time」の名前が付けられた
wikipediaより https://ja.wikipedia.org/wiki/%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9RubyのJITは
MJIT
という名前がよく出ていますが、しくみを理解する上ではRubyのJITの実装周りの総称として覚えておいて良さそうです。
参考:https://k0kubun.hatenablog.com/entry/ruby26-jitこれまでとの処理の違い
Ruby1.9~Ruby2.5 ,デフォルト設定のRuby2.6での実行
Rubyはインタープリタ型言語なので、コンパイルして直接機械語に変換されるわけではありません。どうやって実行されているというと、Rubyのコードは字句・構文解析を経て
YARVバイトコード
というものに変換されます。バイトコードはプログラム言語と機械語との中間にあたるようなコードです。でも
YARVバイトコード
はあくまでバイトコードであって機械語ではないので、CPUはこのYARVバイトコード
を直接解釈して実行することはできません。
そこで、CPUに変わってYARVバイトコード
を解釈し、CPUに命令を発行してくれるのがバーチャルマシン(VM)であるYARV
です。こうしてRubyのコードは、明示的な機械語へのコンパイルを必要とせずに、実行することができます。
MJITを有効にしたRuby2.6
(これらの理解の拠り所として Cコンパイラを利用したRubyのJITコンパイラ を参考にさせていただきました。)
YARVバイトコード
が生成されるところまではこれまでと変わりません。
そして、バーチャルマシンであるYARV
もこれまで通り登場しますが、JITコンパイラー
という役者が増えています。あるプログラムが実行されたとき、
YARV
(以下VM) は生成されたYARVバイトコード
を解釈し、CPUが理解できる命令を発行し、実行してくれます。ここで、あるメソッドが5回以上呼ばれたとします。そのときVMのスレッドは、JITのキューに、このメソッドを積みます。
JITはVMとは別のスレッドで動いて、積まれたメソッドをYARVバイトコード
からCのコードに変換します。
生成されたCのコードはやがて機械語
に変換され.soファイル
が生成されます。
.soファイル
の中身はバイナリコード(=機械語)です。これは動的にVMから呼ばれるようにリンクされます。なので、もし次のタイミングでVMが処理しようとしている
YARVバイトコード
の中に、先ほどJITコンパイラ
が処理したのと同じメソッドがあった場合、
VMはYARVバイトコード
を解釈してメソッドを実行するのではなく、機械語にコンパイル済みのメソッド.soファイル
を関数ポインタを通じて読み込むことで、より高速に同メソッドを実行することができます。現状での速さ/ベンチマーク
JITの仕組みによりRubyの高速化が図られたわけですが、いったいどれだけ早くなったのかというデータは検証方法によってバラツキがあるようです。
これはCコードを生成する際の最適化、Cコードから機械語に翻訳する際の最適化など、様々な要素が絡みあっているからのようで、単純に「何倍早くなった!」と言えるわけではないようでした。
また、JITを有効にして
Ruby on Rails
で作成したWebアプリケーションを実行すると、かえって遅くなるようなデータもrubykaigiでは示されていました。
ただ最新の実験ではJITを有効化しても、無効化時と同程度のスコアが出るようにはなったそうです....ここからが本番。(2019/4 rubykaigi)(これは筆者の感覚的理解ですが、単純に処理が増えたのだからそれは遅くなっても仕方なさそうだし、プログラムの実行時間が長くなり機械語にコンパイル済みのメソッドが増えれば増えるほど高速化してゆくようなパラダイムでもあるし....納得、という感じです。)
さらに、JIT以外の速度改善についても、「rubyのインタープリタをrubyで書く」といった内容があり、非常に興味のそそるものでした。RubyKaigi 2019: Write a Ruby interpreter in Ruby for Ruby 3
おわりに
rubykaigiを振り返ると自分はいかにRubyを知らなかったのか思い知らされます。日常的にRailsに触っていると、Railsが行ってくれている魔術が当たり前になってしまうことがあったかもしれません。
一方、周りを見渡すと、Railsを使いながらもRubyで内製ツールを作って開発効率を上げている例が多くあることを知り、そういった活動が組織の技術力を作り、またOSSの活動へと広がってゆくことを実感しました。Rubyというプログラミング言語を通し、技術に向き合うとうことを再確認したrubykaigiの3日間でした。
参考にた書籍/Webページ
Rubyのしくみ -Ruby Under a Microscope-
Ruby 2.6にJITコンパイラをマージしました|k0kubun's blog
プロと読み解く Ruby 2.6 NEWS ファイル|クックパッド開発者ブログ
Cコンパイラを利用したRubyのJITコンパイラ / Programming Symposium 60
LITALICOではエンジニアを積極採用中です。
新卒・第二新卒(未経験含)/中途採用、いずれも行なっていますので、ご興味のある方は下記URLをご確認ください。
https://www.wantedly.com/projects/309158
- 投稿日:2019-05-24T12:37:17+09:00
devise で基本的なアカウント機能のみを実装してみた
はじめに
アカウント機能を容易に実装することができる gem である devise の基本的な使用方法を
数回に分けて投稿します。devise gem には様々な機能がありますが、
今回はあえて基本的なアカウント機能のみを実装してみようと思います。動作対象
・Ruby 2.5
・Rails 5.1
・SQLite3今回実装する機能
・アカウント作成機能
・ログイン及びログアウト機能
・アカウント編集機能devise とは?
devise はログイン・ログアウト機能やアカウント作成機能などを簡単に実装できる gem です。
機能がモジュール化されているので、管理しやすいのが特徴です。
今回は2018/3/18にリリースされた4.4.3を使っていきます。devise のインストール
まずは雛形を作成しましょう。
私はいつもディレクトリを真っ先に作成して、そのディレクトリに移動して、
Gemfile を作成するという手順を取っています。今回は devise_learning というアプリを開発していきます。
# 任意のディレクトリに移動して、devise_learning ディレクトリを作成する $ mkdir devise_learning # devise_learning ディレクトリに移動する $ cd devise_learning # Gemfile を作成する $ bundle init次に、Gemfile に今回使用する gem を貼り付けます。
Gemfile# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem 'rails', '~> 5.1.5' gem 'turbolinks', '5.2.0' gem 'puma', '~> 3.7' # database gem 'sqlite3', '~> 1.3.6' # devise gem 'devise', '4.4.3' # gem 'bcrypt', git: 'https://github.com/codahale/bcrypt-ruby.git', require: 'bcrypt' group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end group :development do gem 'web-console', '>= 3.3.0' gem 'listen', '3.1.5' end # windows 環境の方は以下のコメントを外してください # gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]devise はパスワードを暗号化する際に bcrypt という gem を使用しています。
ruby のバージョンが 2.3 系の場合、あるいは Windows 環境で本アプリを作成する場合は、
デフォルトでインストールされる bcrypt が動作しない場合があります。
動作しない場合はこのコメントを外してください。Gemfile# gem 'bcrypt', git: 'https://github.com/codahale/bcrypt-ruby.git', require: 'bcrypt'
完了したら
bundle install
をしましょう。$ bundle installここでアプリケーションの雛形を作成します。
既に gem はインストールしたので、-B
を付属してbundle install
をスキップします。$ rails new ./ -BGemfile の対応について尋ねられると思いますが、もちろん
n
と答えてください。
アプリケーションの雛形が完成したら、とりあえずアプリを起動してみましょう。$ rails server
「Yay! You’re on Rails!」と表示されていれば OK です。
devise のインストール
早速、devise を雛形にインストールしましょう。
rails g devise:install
を実行すると devise の導入手順が出力されるので、これを元に進めていきます。$ rails g devise:install 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 ===============================================================================先ほどのコマンドで 2 つのファイルが作成されましたが、これらのファイルの使い方は後ほど解説します。
まずは 1 番目の説明を読んでみましょう。devise の導入手順の和訳 (1)
環境ファイルにデフォルトの URL オプションが定義されていることを確認してください。
config/environments/development.rb の開発環境に適した default_url_options の例を次に示します:config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
プロダクション環境では、host: はアプリケーションの実際のホストに設定する必要があります。
config/environment/development.rb に defalut_url_options を設定しましょう。
default_url_options については Rails チュートリアルの 11.2.2 でも取り扱われています。
私はローカル環境で開発しているので、devise の導入手順の通りに記述します。config/environment/development.rbRails.application.configure do # . # . # . config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } # . # . # . endメール認証機能は今回実装しないので、これ以上の設定は行いません。
終わったら 2 番目の説明を読んでみましょう。devise の導入手順の和訳 (2)
config/routes.rb に何らかの root_url を定義していることを確認してください。
例えば:root to: "home#index"
config/routes.rb に root_url を定義しましょう。
まずは controller を定義します。
今回は devise の導入手順に示されている例のとおり、
ホーム画面用の home コントローラと index ページを用意します。$ rails g controller home index
次は config/routes.rb に root_url を設定します。
自動で作成されるルーティングを次のように書き換えてください。config/routes.rbRails.application.routes.draw do root to: 'home#index' end以上で root_url の定義は完了です。
終わったら 3 番目の説明を読んでみましょう。devise の導入手順の和訳 (3)
app/views/layouts/application.html.erb にフラッシュメッセージがあることを確認してください。
例えば:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
任意のフラッシュメッセージを定義していきましょう。
といっても、今回は app/views/layouts/application.html.erb に、
導入手順にかかれているコードをそのまま貼り付けてしまいます。app/views/layouts/application.html.erb<!DOCTYPE html> <html> <head> <title>DeviseLearning</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <div class="flash"> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> </div> <%= yield %> </body> </html>bootstrap を使用したスタイルの調整などは今回行わないので、以上で任意のフラッシュメッセージの定義は完了です。
終わったら 4 番目の説明を読んでみましょう。devise の導入手順の和訳 (4)
以下のコマンドを実行することによって、
Deviseビュー(カスタマイズ用)をアプリにコピーすることができます:rails g devise:views
Devise ビュー(カスタマイズ用)をアプリにインストールしてみましょう。
導入手順に記されているコマンドを実行すると大量の Devise ビューが作成されます。$ rails g devise:views invoke Devise::Generators::SharedViewsGenerator create app/views/devise/shared 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
作成されたファイルで、ログインやアカウント作成時などで使用される初期ビューを変更することができます。
今回は変更しません。
以上で devise の初期設定は完了です。これでアカウント機能をもつテーブルを作成する手順が整いました。devise を用いてアカウント機能を実装
devise を用いてアカウント機能をもつ users モデルを作成していきます。
devise でアカウントを持つモデルを作成する場合は次のコマンドを実行します。$ rails g devise user invoke active_record create db/migrate/xxxxxxxxxxxxxx_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まずは、
route devise_for :users
からみていきます。
config/routes.rb が次の通りになっていることを確認してください。config/routes.rbRails.application.routes.draw do devise_for :users root to: 'home#index' end
devise_for :users
というルートが設定されていることが確認できます。
どんなルートが作成されているのか確認してみましょう。$ 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 / home#index
devise_for :users
は使用されているモジュールに応じて、devise の機能に必要なルートを設定します。
使用するモジュールは app/models/user.rb で設定します。
次はcreate app/models/user.rb
をみてみましょう。
devise のモジュールはこのファイルで管理します。app/models/user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable enddevise に実装されている各モジュールの機能をここに記載します。
devise modules 機能 database_authenticatable サイン時にパスワードを暗号化してDBに登録 registerable ユーザーが自身のアカウントの編集と削除を可能にする recoverable パスワードのリセットを可能にする rememberable Remember Me 機能を有効化する trackable サインインの回数やIPアドレスなどを記録 validatable メールとパスワードのバリデーションを行う confirmable メール認証機能を有効化 lockable 規定回数ログインに失敗したらアカウントをロックする timeoutable 一定時間でセッションを破棄する omniauthable Twitter や Facebook など、外部サービスのアカウントで認証を可能にする 今回は現時点で使用しないモジュールを全てコメントアウトします。
app/models/user.rbclass User < ApplicationRecord # Not use devise modules are: # :recoverable, :trackable, :confirmable, # :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :rememberable, :validatable end使用するモジュールを 4 つに絞りました。
この 4 つのモジュールだけで Rails チュートリアルの 10 章 までの機能を実装することができます。
ここでもう一度ルートを確認してみてください。先ほど存在していたルートの一部がなくなっているはずです。$ 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 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 / home#index次に進む前に、ログインやログアウトなどに必要なリンクを作成しておきましょう。
rails routes
の出力からリンクを作成します。
app/wiews/layouts/_session.html.erb
を新たに作成して次のように記述してください。app/wiews/layouts/_session.html.erb<% if user_signed_in? %> <p><%= link_to "アカウント編集", edit_user_registration_path %> <p><%= link_to "ログアウト", destroy_user_session_path, method: "delete" %></p> <% else %> <p><%= link_to "ログイン", new_user_session_path %></p> <p><%= link_to "アカウント作成", new_user_registration_path %></p> <% end %>
user_signed_in?
はセッションを登録しているかどうかを真理値で返してくれる、
devise が提供するメソッドです。
user
の箇所は作成したテーブル名で変化するので注意してください。
app/views/layouts/application.html.erb
にこのパーシャルを追記しましょう。app/views/layouts/application.html.erb<!DOCTYPE html> <html> <head> <title>DeviseLearning</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <div class="flash"> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> </div> <%= render 'layouts/session' %> <%= yield %> </body> </html>次は
create db/migrate/xxxxxxxxxxxxxx_devise_create_users.rb
を見ていきましょう。
※ xxxxxxxxxxxxxxには作成日時が入ります。このマイグレーションファイルには、アカウント機能で使用されるテーブルが用意されています。
db/migrate/xxxxxxxxxxxxxx_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.1] 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今回は
app/models/user.rb
で設定した 4 つのモジュールに必要なカラムだけを作成します。
現時点で使用しないカラムをコメントアウトしましょう。db/migrate/xxxxxxxxxxxxxx_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.1] 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この作業が完了したらデータベースを作成して、マイグレートしましょう。
$ rails db:migrate
db/schema.rb
をみると、
アカウント機能を実現するのに必要な最低限のカラムが用意されていることがわかります以上で基本的なアカウント機能は全て実装できました
「え?これだけ?」
はい、本当にこれだけです。
これだけでアカウント登録・編集・削除、ログイン・ログアウト、Remember Me 機能の実装が完了です。
サーバを起動してみて、実際に挙動を確認してみてください。さいごに
今回は基本的なアカウント機能を devise を使って実装しました。
devise を扱う際、必要以上にカラムを作らないように気をつけましょう。参照・参考
Rails チュートリアル 11.2.2
Rails チュートリアル 10章
STEP21:Rails5にdeviseでログイン機能を実装しよう! #Rails #Ruby | TickleCode
- 投稿日:2019-05-24T11:49:35+09:00
jsフレームワークstimulusでyahoo地図を操作してみた(その一)
「railsアプリで、stimulusを使ってyahoo地図を操作する方法」についての纏めーー
当初はgooglemapで試してみるつもりだったが、料金請求が怖そうなのでyahoo地図に切り替えた(操作自体は似ているので、ちょっと書き換えればgooglemapにも簡単に切り替えられるはず。実際、【googlemap操作について書かれたサイト】も複数参照して書いています)。やったこと、やる予定のこと
0. railsアプリへのstimulus適用(以前書いた記事)
1. stimulusでyahoo地図を表示(←今ここ)
2. 表示した地図上で、指定座標に移動
……この段階では「指定座標」はコードに直接書いています。
座標取得の方法は「3」以降でやる予定。3. 住所から座標を取得し、それをDBに登録(そのうち書く)
4. 「3」で登録した情報を選択し、yahoo地図上でその場所に移動(そのうち書く)
【事前準備】railsにstimulusを適用し、yahooのアプリケーションIDも取得しておく
stimulus適用はこちら(自記事)やこちら、アプリケーションID取得はこちらを参照。
とりあえず、地図を表示させてみる。
公式サイトを参照つつ……
まずはアプリケーションビューで、
app/views/layouts/application.html.erb<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application' %> <%# ↓これを追記↓ %> <script type="text/javascript" charset="utf-8" src="https://map.yahooapis.jp/js/V1/jsapi?appid=【取得した自分のID】"></script> <%# ↑これを追記↑ %>と【yahooのAPIを使用する】旨を記述
……APIを使用するビューだけに個別に書いても問題ない(というか、アクセス回数制限を考え得るならそちらのほうがいい)のだろうけど、今回は面倒なので全部に適用させることにした。で、地図を表示したい場所(ビュー、今回はusers/testを作ってそこでやりました)に
app/views/users/test.html.erb<!-- ↓①↓ --> <div data-controller="moving"> <p>Google Maps APIを使ったサンプルです。</p> <!-- ↓②↓ --> <div id="idmeihatekitoudemoiiyo" style="width:500px; height:300px" data-target="moving.map" ></div> </div>と記述。
①stimulus用に【div要素の枠、「data-controller="moving"」】を作成。
②「①」の枠の中に配置した【ヤフー地図を表示するためのdiv要素……「div id="map"」】に【「map」というターゲット名……「data-target="moving.map"」】を設定する。
(stimulusだと要素はデータターゲット名で指定可能なので、「②」のdiv要素のid名は適当でも問題なくなります(いやでも、これは適当すぎだろう)。最後に、対応するstimulusファイルを作成。
↑で「data-controller="moving"」としているので、ファイル名は「moving_controller.js」となります。
app/javascript/controllers/moving_controller.jsimport { Controller } from "stimulus" export default class extends Controller { static targets = [ "map" ] initialize() {//←① this.map = new Y.Map(this.mapTarget.id);//←② this.map.drawMap(new Y.LatLng(35.66572, 139.73100), 17, Y.LayerSetId.NORMAL);//←③ //↓④ var center = new Y.CenterMarkControl var control = new Y.LayerSetControl(); this.map.addControl(center); this.map.addControl(control); } }①イニシャライズ時に、
②【ビュー側で「data-target="moving.map"」としている要素】のidを使って、「this.map」を設定
……ここはid名を直接記入しても、問題はないです。ただ要素の指定はターゲット名で統一したほうが、なんかカッコイイ気がする(いや、id名が異なる違う要素にも使い回せますし)。
③「this.map」にヤフー地図を書き込む(「drawMap」。「35.66572, 139.73100」が初期表示する座標、お好みで変更してください。
④お好みでレイヤーを設定( 公式サイトにある「レイヤーセットID」をご参照ください)。これで、地図が画面に表示されるように……
なりました。
とりあえずは今回はここまで。続きはこちらです。
- 投稿日:2019-05-24T10:42:48+09:00
ドット記法で値にアクセスできない…
RailsのActive Recordにおいて、例えば、Userモデルのインスタンスはドット記法を用いてその属性値にアクセスできることはご存知かと思います。
例) >> user = User.new(name: "hoge", email: "hoge@example.com") >> user.name => "hoge"ですが、ドット記法を用いて属性値にアクセスできないことがあったので、シェアしたいと思います。
バージョン
Rails:5.1.4
ruby:ruby 2.6.0p0状況
例えば、勤怠を管理するモデルでAttendanceモデルがあるとする。
id time_in time_out time_in_change time_out_change 1 09:00 18:00 10:00 19:00 ︙ ︙ ︙ ︙ ︙ この時、
例) >> attendance = Attendance.find(1) >> attendance.time_in => "09:00"となり、ドット記法でアクセスできるが、time_in_change及びtime_out_changeカラムの場合は、
例) >> attendance = Attendance.find(1) >> attendance.time_in_change => nilでドット記法で属性値にアクセスできない現象が発生しました。
似たような現象が起こっている記事を発見し、→ 参考記事
試しに、
例) >> attendance = Attendance.find(1) >> attendance.attributes['time_in_change'] => "10:00"でドット記法で値にアクセスできるが、なぜなのか原因が分からず…
さらに、試みて
time_in_change → change_time_in
time_out_change → change_time_out
とカラム名を変更すると、例) >> attendance = Attendance.find(1) >> attendance.change_time_in => "10:00"となり、attributesを使用しなくても値にアクセスできるようになりました。
カラム名の命名に問題があるのでしょうか?最後に
今回の現象について、原因が何であるか分からないため、この記事を読んでくださった方で
何か原因が思い当たるようでしたら、アドバイス頂けると幸いです。
よろしくお願いします。@sakuro さん
@scivola さん
よりご指摘頂きました。どうやら、
今回の場合、time_inカラムが存在していて、
.time_in_changeとすると、.time_in_changeカラムの属性値に
アクセスするのではなく、ActiveModel::Dirtyモジュールに定義されている
time_in_changeメソッドが呼び出されるみたいです。
※カラム名_changeで呼び出される。今回の属性値にアクセスできるか否かの件とは関係ありませんが、
ActiveModel::Dirtyは、変更前の値を追跡できたりするので、
かなり便利ですね!
- 投稿日:2019-05-24T10:29:00+09:00
carrierwave+S3で本番環境への画像アップロード機能実装
はじめに
プロフィール画像をアップロードする機能を
carrierwave
とmini_magick
、fog-aws
を用いてAWS S3
にアップロードするまでの設定方法を書いていく。
本記事では前提としてユーザー管理にdevise
、ビューにはSlim
を使用しており、AWS S3
でバケットの作成が済んでいる状態で進める。準備
あらかじめ本記事で用いる
gem
をインストールしておく。Gemfilegem 'carrierwave' gem 'mini_magick' gem 'fog-aws'Terminalbundle
また、
Userモデル
にプロフィール画像保存用のカラムとして、avatarカラム
を追加しておく。
コマンドでbin/rails g migration AddAvatarToUsers avatar:string
を入力し、マイグレーションファイルを生成。2019***********_add_avatar_to_users.rbclass AddAvatarToUsers < ActiveRecord::Migration[5.2] def change add_column :users, :avatar, :string end endTerminalbin/rails db:migrate
手順
carrierwaveの設定
まずは画像のアップローダーを作成します。
コマンドでbin/rails g uploader Avatar
と入力。
ここでは最低限の設定をしていきます。app/uploaders/avatar_uploader.rbclass AvatarUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick # 環境毎の画像保存先 if Rails.env.development? storage :file elsif Rails.env.test? storage :file else storage :fog end # S3のディレクトリ名 def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end # 許可する画像の拡張子 def extension_whitelist %w(jpg jpeg gif png) end # 保存するファイルの命名規則 def filename "#{secure_token}.#{file.extension}" if original_filename.present? end endまた、
user.rb
に以下のコードを追記し、Avatarカラム
とアップローダーを紐づけ。user.rbclass User < ApplicationRecord mount_uploader :avatar, AvatarUploade続いて
config/initializers/carrierwave.rb
を作成し、AWS S3
の設定書いていく。
credentialの設定方法については、credentials.yml.encでシークレットキーを管理にまとめた。config/initializers/carrierwave.rbif Rails.env.production? CarrierWave.configure do |config| config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', aws_access_key_id: Rails.application.credentials.dig(:aws, :access_key_id), aws_secret_access_key: Rails.application.credentials.dig(:aws, :secret_access_key), #S3のリージョン #ap-northeast-1はアジアパシフィック(東京) region: 'ap-northeast-1' } # S3のバケット名 config.fog_directory = 'hogehoge' # S3に保存しておく期間 config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" } end endストロングパラメータの設定
avatar
に要素が入った状態でUserモデル
が更新されるのを許可するために、ストロングパラメータの設定をする。application_controller.rbprotected def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:username]) devise_parameter_sanitizer.permit(:account_update, keys: [:username, :description, :avatar]) endビューの設定
app/views/devise/registrations/edit.html.slim.circle-avatar.field label for="user_avatar" | プロフィール画像 #img_field onclick="$('#file').click()" - if current_user.persisted? && current_user.avatar? = image_tag current_user.avatar.to_s = f.file_field :avatar, style: "display:none;" - else = image_tag "no_avatar.png" = f.file_field :avatar, style: "display:none;"
current_user
はdevise
の独自メソッド。
ログインユーザーがプロフィール画像を設定している場合はそれを表示し、設定していない場合に表示する画像ファイル(ここではno_avatar.png
はあらかじめ用意する
display:none;
とすることで、プロフィール画像をクリックすると画像選択ができるようにする。app/assets/stylesheets/users.scss.circle-avatar.field img { width: 100px; height: 100px; border-radius: 50%; object-fit: cover; } #img_field:hover { transition: 0.5s ease-out; opacity: 0.5; }プロフィール画像を丸く表示し、ホバー時に半透明になるよう設定する。
最後に選択された画像を表示するための設定をする。
users.js$(document).on("turbolinks:load", function(){ $fileField = $('#file') $($fileField).on('change', $fileField, function(e) { file = e.target.files[0] reader = new FileReader(), $preview = $("#img_field"); reader.onload = (function(file) { return function(e) { $preview.empty(); $preview.append($('<img>').attr({ src: e.target.result, width: "100%", class: "preview", title: file.name })); }; })(file); reader.readAsDataURL(file); }); });参考
【Rails5】Deviseのregistrations#editで画像をアップロードする
Railsでcarrierwaveを使ってAWS S3に画像をアップロードする手順を画像付きで説明する!
- 投稿日:2019-05-24T09:26:05+09:00
Rails6 のちょい足しな新機能を試す23(I18n fallbacks編)
はじめに
Rails 6 に追加されそうな新機能を試す第23段。 今回のちょい足し機能は、
I18n fallbacks
編です。
Rails 6.0 では、config.i18n.fallbacks
の設定で、明示的に I18n.default_locale を fallback として指定していないとDEPRECATION WARNING が出ます。fallbacks の挙動は、Rails 6.0 と Rails 6.1 で違いがあると思われます。Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は
gem install rails --prerelease
でインストールできます。$ rails --version Rails 6.0.0.rc1Rails プロジェクトを作る
$ rails new rails6_0_0rc1 $ cd rails6_0_0rc1Controller と View を作る
今回はモデルなしで、試します。
$ bin/rails g controller i18n_fallbacks index
db:create をしておく
$ bin/rails db:create
development.rb
に fallbacks の設定を追加する
config/environments/development.rb
に fallbacks を設定します。config/environments/development.rbRails.application.configure do ... config.i18n.fallbacks = [{de: :ja}] endlocale 変換用のファイルを用意する
英語(en)、日本語(ja)、ドイツ語(de) の3つを用意します。
1つの yml ファイルに全部の訳語が揃ってしまうと fallbacks の動作を確認できないので、揃わないようにします。config/locales/en.ymlen: morning: "Good Morning"config/locales/ja.ymlja: afternoon: こんにちはconfig/locales/de.ymlde: night: Gute Nacht
index.html.erb
を編集する本来、 with_locale は、ApplicationController で使うべきだと思いますが、今回は手抜きで View だけでやります。
app/views/i18n_fallbacks/index.html.erb<% I18n.with_locale(:de) do %> <h1>I18n Fallbacks Test</h1> <h2>I18n settings</h2> <ul> <li>I18n.default_locale = <%= I18n.default_locale %></li> <li>I18n.locale = <%= I18n.locale %></li> <li>I18n.fallbacks = <%= I18n.fallbacks %></li> </ul> <h2>I18n translations</h2> <ul> <li>morning=<%= t(:morning) %></li> <li>afternoon=<%= t(:afternoon) %></li> <li>night=<%= t(:night) %></li> </ul> <% end %>
rails server
を実行してブラウザで表示する
rails server
を実行して、 http://localhost:3000/i18n_fallbacks/index にアクセスします。
ログに DEPRECATION WARNING が表示されます。DEPRECATION WARNING: Using I18n fallbacks with an empty `defaults` sets the defaults to include the `default_locale`. This behavior will change in Rails 6.1. If you desire the default locale to be included in the defaults, please explicitly configure it with `config.i18n.fallbacks.defaults = [I18n.default_locale]` or `config.i18n.fallbacks = [I18n.default_locale, {...}]`. If you want to opt-in to the new behavior, use `config.i18n.fallbacks.defaults = [nil, {...}]`. (called from <main> at /app/config/environment.rb:5)fallbacks の設定を変更する
fallbacks の設定を変更してみます。明示的に
I18n.default_locale
を追加します。config/environments/development.rbRails.application.configure do ... config.i18n.fallbacks = [I18n.default_locale, {de: :ja}] end再度
rails server
を起動し直して、ブラウザでページを表示すると今度は、DEPRECATION WARNING
が表示されません。
ブラウザの表示内容は変わりません。
DEPRECATION WARNING の意味するところは、「I18n.default_locale
を設定していなくても、Rails 6.0 では、 Rails内部でI18n.default_locale
を追加するけど、Rails6.1 では挙動が変わるので、明示的にI18n.default_locale
を追加するようにしてね」ということみたいです。fallbacks の設定を再度変更する
DEPRECATION WARNING の最後に
If you want to opt-in to the new behavior, use `config.i18n.fallbacks.defaults = [nil, {...}]
とありますので、恐らくこれが、Rails 6.1 での挙動になると思われます。設定を変更して試してみます。
config/environments/development.rbRails.application.configure do ... config.i18n.fallbacks = [nil, {de: :ja}] endブラウザの表示内容が以下のように変わります。
en (I18n.default_locale) が fallback のリストから消えています。
また、morning
がGoog Morning
に変換されていません。
fallbacks の設定を再度変更する
自動生成された
config/environments/production.rb
では、config/environments/production.rbconfig.i18n.fallbacks = trueとなっているので、 fallbacks が true のときにどうなるのか確認します。
このときは、
I18n.default_locale
が fallbacks に含まれています。
まとめ
Rails 6.0 で DEPRECATION WARNING が出た場合は、
I18n.default_locale
を設定するのが良さそうです。試したソース
試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try023_i18n_fallbacks参考情報
- 投稿日:2019-05-24T01:52:58+09:00
「Rails」タグについて(タグシノニム: 「RubyOnRails」「Ruby-Rails」「rails」「ROR」「#rubyonrails」)
対象
説明
DRY(Don't Repeat Yourself, 同じことを繰り返さない)とCoC(Convention over Configuration, 設定より規約)を基本理念としたRuby製のWebアプリケーションフレームワーク
github
https://github.com/rails/rails
タグシノニム
https://qiita.com/tags/RubyOnRails
https://qiita.com/tags/Ruby-Rails
https://qiita.com/tags/rails
https://qiita.com/tags/ROR
https://qiita.com/tags/#rubyonrails
https://qiita.com/tags/Railsチュートリアル : 厳密なシノニムではありませんが、参考情報としてバージョン違い
https://qiita.com/tags/Rails4
https://qiita.com/tags/Rails4.2
https://qiita.com/tags/Rails5
https://qiita.com/tags/Rails5.0.0.1
https://qiita.com/tags/Rails5.1.2
https://qiita.com/tags/Rails5.2.2
https://qiita.com/tags/Rails6
https://qiita.com/tags/RubyOnRails5.2
https://qiita.com/tags/RubyonRails4.0
https://qiita.com/tags/RubyonRails5.0.0.1
https://qiita.com/tags/rails3
https://qiita.com/tags/rails3.2
https://qiita.com/tags/rails4.0
https://qiita.com/tags/rails4.1
https://qiita.com/tags/rails5
https://qiita.com/tags/rails5.1
https://qiita.com/tags/rails5.2
- 投稿日:2019-05-24T01:52:58+09:00
「Rails」タグについて(タグシノニム: 「RubyOnRails」「Ruby-Rails」「rails」「ROR」)
対象
説明
DRY(Don't Repeat Yourself, 同じことを繰り返さない)とCoC(Convention over Configuration, 設定より規約)を基本理念としたRuby製のWebアプリケーションフレームワーク
タグシノニム
https://qiita.com/tags/RubyOnRails
https://qiita.com/tags/Ruby-Rails
https://qiita.com/tags/rails
https://qiita.com/tags/RORバージョン違い
https://qiita.com/tags/Rails4
https://qiita.com/tags/Rails4.2
https://qiita.com/tags/Rails5
https://qiita.com/tags/Rails5.0.0.1
https://qiita.com/tags/Rails5.1.2
https://qiita.com/tags/Rails5.2.2
https://qiita.com/tags/Rails6
https://qiita.com/tags/RubyOnRails5.2
https://qiita.com/tags/RubyonRails4.0
https://qiita.com/tags/RubyonRails5.0.0.1
https://qiita.com/tags/rails3
https://qiita.com/tags/rails3.2
https://qiita.com/tags/rails4.0
https://qiita.com/tags/rails4.1
https://qiita.com/tags/rails5
https://qiita.com/tags/rails5.1
https://qiita.com/tags/rails5.2
- 投稿日:2019-05-24T01:28:22+09:00
Rails コマンドの実行の流れをたどる旅
Rails コマンド群がどのように呼び出されているのか気になったので、ソースコードを追いながらその流れを整理してみました。うーんとっても長い。
とっても長いので、ざっくり要約を載せておきます。
- Rails コマンドはそれぞれコマンド名に対応したファイルを実行する
- 例) rails generate → /rails/railties/lib/rails/commands/generate/generate_command.rb
もう、これだけ!後はホントに沼なので書く気にならん!w
さて、ここから先は自分の頭の中の思考をダダ漏らしにした感じでお送りするので、暇な方で覗いてみてください。
まずは
rails
コマンドの実行パスを確認します。こいつが何をしてるかっていう話ですよね。$ which rails => /usr/local/bundle/bin/rails実行パスが分かったので
cat /usr/local/bundle/bin/rails
を実行して中身を確認します。/usr/local/bundle/bin/rails#!/usr/bin/env ruby # # This file was generated by RubyGems. # # The application 'railties' is installed as part of a gem, and # this file is here to facilitate running it. # require 'rubygems' version = ">= 0.a" str = ARGV.first if str str = str.b[/\A_(.*)_\z/, 1] if str and Gem::Version.correct?(str) version = str ARGV.shift end end if Gem.respond_to?(:activate_bin_path) load Gem.activate_bin_path('railties', 'rails', version) else gem "railties", version load Gem.bin_path("railties", "rails", version) end
Gem.activate_bin_path('railties', 'rails', version)
Gem.bin_path("railties", "rails", version)
このファイルでは上記のどちらかを実行して終わってるんですけど、どちらも同じ値
"/usr/local/bundle/gems/railties-5.2.3/exe/rails"
を返してます。Gem.activate_bin_path('railties', 'rails', '>= 0.a') # => "/usr/local/bundle/gems/railties-5.2.3/exe/rails" Gem.bin_path('railties', 'rails', '>= 0.a') # => "/usr/local/bundle/gems/railties-5.2.3/exe/rails"ということなので、このパスでロードして読み込まれるコードの中身を見てみます。
/railties/exe/rails#!/usr/bin/env ruby # frozen_string_literal: true git_path = File.expand_path("../../.git", __dir__) if File.exist?(git_path) railties_path = File.expand_path("../lib", __dir__) $:.unshift(railties_path) end require "rails/cli"/railties/lib/rails/cli.rb# frozen_string_literal: true require "rails/app_loader" # If we are inside a Rails application this method performs an exec and thus # the rest of this script is not run. Rails::AppLoader.exec_app require "rails/ruby_version_check" Signal.trap("INT") { puts; exit(1) } require "rails/command" if ARGV.first == "plugin" ARGV.shift Rails::Command.invoke :plugin, ARGV else Rails::Command.invoke :application, ARGV endここでコメントアウトに
If we are inside a Rails application this method performs an exec and thus
the rest of this script is not run.と書いてあるので、 Rails アプリケーション内から読み出した場合は、
Rails::AppLoader.exec_app
以降のコードは実行されないようです。ので、この #exec_app メソッドの中身を見てみます。/railties/lib/rails/app_loader.rb# frozen_string_literal: true require "pathname" require "rails/version" module Rails module AppLoader # :nodoc: extend self RUBY = Gem.ruby EXECUTABLES = ["bin/rails", "script/rails"] BUNDLER_WARNING = <<EOS …(長いので省略)… EOS def exec_app original_cwd = Dir.pwd loop do if exe = find_executable contents = File.read(exe) if contents =~ /(APP|ENGINE)_PATH/ exec RUBY, exe, *ARGV break # non reachable, hack to be able to stub exec in the test suite elsif exe.end_with?("bin/rails") && contents.include?("This file was generated by Bundler") $stderr.puts(BUNDLER_WARNING) Object.const_set(:APP_PATH, File.expand_path("config/application", Dir.pwd)) require File.expand_path("../boot", APP_PATH) require "rails/commands" break end end # If we exhaust the search there is no executable, this could be a # call to generate a new application, so restore the original cwd. Dir.chdir(original_cwd) && return if Pathname.new(Dir.pwd).root? # Otherwise keep moving upwards in search of an executable. Dir.chdir("..") end end def find_executable EXECUTABLES.find { |exe| File.file?(exe) } end end endここちょっとビックリしたんですけど、
extend self
を書いとくと自身に特異メソッドを生やすことできるっぽい。だからRails::AppLoader.exec_app
を直接実行できるのね。へー…。ここでは、
EXECUTABLES = ["bin/rails", "script/rails"]
このどちらかのパスが存在したらそれを実行というロジックになってます。bin/rails
の中身を見てみます。/path/to/work_dir/bin/rails#!/usr/bin/env ruby begin load File.expand_path('../spring', __FILE__) rescue LoadError => e raise unless e.message.include?('spring') end APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands'みたところ、
APP_PATH
かENGINE_PATH
がなかった場合でも定数APP_PATH
の初期化とconfig/application.rb
とconfig/boot.rb
の読み込み、そして最終的にrails/commands
を読み込んでいるっぽい。/railties/lib/rails/app_loader.rbObject.const_set(:APP_PATH, File.expand_path("config/application", Dir.pwd)) require File.expand_path("../boot", APP_PATH) require "rails/commands"ということで、
rails/commands
の中身を見てみます。/railties/lib/rails/commands.rb# frozen_string_literal: true require "rails/command" aliases = { "g" => "generate", "d" => "destroy", "c" => "console", "s" => "server", "db" => "dbconsole", "r" => "runner", "t" => "test" } command = ARGV.shift command = aliases[command] || command Rails::Command.invoke command, ARGVうおお、ここで Rails コマンドのエイリアスが出てきたぞ…。ここで
ARGV
に含まれていた値を取り出して、それを引数にRails::Command.invoke command, ARGV
を実行してます。ここで言う
ARGV
は「 Ruby スクリプトに与えられた引数を表す配列」とのことで、実行ファイル名より後の値を配列でファイル内に渡してます。
https://docs.ruby-lang.org/ja/2.6.0/method/Object/c/ARGV.htmlたとえば、
$ rails generate modelの実行時の引数
ARGV
は以下のように保存されます。['generate', 'model']さて、
Rails::Command.invoke command, ARGV
の中身を見てみます。
たとえば、rails generate model
を実行する場合、full_namespace # => 'generate' args # => ['model']がそれぞれ代入されているはずです。
なお、これ以上の処理は、すべて以下のコマンドを実行する場合を前提とします。$ rails generate model Foo foo:stringよし、 #invoke メソッドの中身見ていきます。
/railties/lib/rails/command.rbdef invoke(full_namespace, args = [], **config) namespace = full_namespace = full_namespace.to_s if char = namespace =~ /:(\w+)$/ command_name, namespace = $1, namespace.slice(0, char) else command_name = namespace end command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name) command_name, namespace = "version", "version" if %w( -v --version ).include?(command_name) command = find_by_namespace(namespace, command_name) if command && command.all_commands[command_name] command.perform(command_name, args, config) else find_by_namespace("rake").perform(full_namespace, args, config) end end前段にいろいろとコマンド名の処理がありますが、最終的には
command.perform(command_name, args, config)
の中身が分かればいいので、まずは変数command
に代入している #find_by_namespace メソッドを見ます。/railties/lib/rails/command.rbdef find_by_namespace(namespace, command_name = nil) # :nodoc: lookups = [ namespace ] lookups << "#{namespace}:#{command_name}" if command_name lookups.concat lookups.map { |lookup| "rails:#{lookup}" } lookup(lookups) namespaces = subclasses.index_by(&:namespace) namespaces[(lookups & namespaces.keys).first] end冒頭 3 行の処理で変数
lookups
には以下の値が代入され、 #lookup メソッドに渡されます。lookups # => ["generate", "generate:generate", "rails:generate", "rails:generate:generate"]それでは #lookup メソッドの中身を見てみます。どうやらここでは
namespaces
を元に各コマンドの処理が入ってるファイルを require してるようです。なるほど、メソッド名通りの処理だ。/railties/lib/rails/command/behavior.rbdef lookup(namespaces) paths = namespaces_to_paths(namespaces) paths.each do |raw_path| lookup_paths.each do |base| path = "#{base}/#{raw_path}_#{command_type}" begin require path return rescue LoadError => e raise unless e.message =~ /#{Regexp.escape(path)}$/ rescue Exception => e warn "[WARNING] Could not load #{command_type} #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" end end end endここでは、 #namespaces_to_paths / #lookup_paths / #command_type 3 つのプライベートメソッドが呼ばれています。各メソッドの中身は単純なので割愛しますが、それぞれ以下の値を返します。
namespaces_to_paths # => ["generate/generate", "generate", "generate/generate/generate", "rails/generate/generate", "rails/generate", "rails/generate/generate/generate"] lookup_paths # => ["rails/commands", "commands"] command_type # => "command"ほんで、
require
が true を返すのは"rails/commands/generate/generate_command"
の時ですね。なんなんだろう、すごいトリッキーなことやってる気がするwそうこうして #find_by_namespace メソッドに戻ってきました。
/railties/lib/rails/command.rbdef find_by_namespace(namespace, command_name = nil) # :nodoc: lookups = [ namespace ] lookups << "#{namespace}:#{command_name}" if command_name lookups.concat lookups.map { |lookup| "rails:#{lookup}" } lookup(lookups) namespaces = subclasses.index_by(&:namespace) namespaces[(lookups & namespaces.keys).first] endさて、次に実行される #subclasses メソッドが謎を呼ぶんですが、中身はこうなっています。
def subclasses @subclasses ||= [] endこれ pry でデバッグ中に試しに実行してみると分かるんですが、なんともう値
[Rails::Command::GenerateCommand]
が入ってます。い、いつ代入されたの…??????って話なんですが、実はrequire path
の時点で代入されていました。そもそも
xxxx_command.rb
の中身はざっくり以下のような構成になっており、必ずRails::Command::Base
クラスを継承するようになっています。module Rails module Command class HogehogeCommand < Base end end endこの
< Base
のタイミングで Rails::Command::Base.inherited が実行されます。メソッドの中身を見ると分かりますが、@subclasses
にbase
が追加されてるのが分かるかと思います。module Rails module Command class Base < Thor class << self … def inherited(base) #:nodoc: super if base.name && base.name !~ /Base$/ Rails::Command.subclasses << base end end … end end end endソースコードリーディングっていろんなファイルを飛び回るからアタマ混乱するなあ…。
さて、その@subclasses
に #index_by をかけて Hash 化します。/railties/lib/rails/command.rbdef find_by_namespace(namespace, command_name = nil) # :nodoc: lookups = [ namespace ] lookups << "#{namespace}:#{command_name}" if command_name lookups.concat lookups.map { |lookup| "rails:#{lookup}" } lookup(lookups) namespaces = subclasses.index_by(&:namespace) namespaces[(lookups & namespaces.keys).first] endここで最後の処理に使う
namespaces
とlookups
の値を確認しておきましょう。現時点でこんな感じになっています。namespaces # => {"rails:generate"=>Rails::Command::GenerateCommand} lookups # => ["generate", "generate:generate", "rails:generate", "rails:generate:generate"]なので、最後の行の返り値はこんな感じになります。
namespaces[(lookups & namespaces.keys).first] # => Rails::Command::GenerateCommandやっと #find_by_namespace(namespace, command_name) の返り値がわかったので、 #invoke メソッドに戻ります。今まで見てきたのは変数
command
にどんな値が代入されるかということでした。上記で見た通り、Rails::Command::GenerateCommand
が代入されることが分かりました。/railties/lib/rails/command.rbdef invoke(full_namespace, args = [], **config) namespace = full_namespace = full_namespace.to_s if char = namespace =~ /:(\w+)$/ command_name, namespace = $1, namespace.slice(0, char) else command_name = namespace end command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name) command_name, namespace = "version", "version" if %w( -v --version ).include?(command_name) command = find_by_namespace(namespace, command_name) if command && command.all_commands[command_name] command.perform(command_name, args, config) else find_by_namespace("rake").perform(full_namespace, args, config) end end仮に
command
が nil だった場合はRails::Command::RakeCommand
を元に rake コマンドが走るようですが一旦それは置いておきます。command.perform(command_name, args, config)
の中身を見ていきます。なお引数の値はこんな感じになっています。command_name # => "generate" args # => ["model"] config # => {}/railties/lib/rails/command/base.rbdef perform(command, args, config) # :nodoc: if Rails::Command::HELP_MAPPINGS.include?(args.first) command, args = "help", [] end dispatch(command, args.dup, nil, config) endここの #dispatch は Rails ではなく Thor のメソッドです。Thor 内部の動きについてはまた色々ありますが、ここでは割愛します。なんだかんだあった後に Rails::Command::GenerateCommand#perform メソッドが呼び出されます。
/railties/lib/rails/commands/generate/generate_command.rbdef perform(*) generator = args.shift return help unless generator require_application_and_environment! load_generators ARGV.shift Rails::Generators.invoke generator, args, behavior: :invoke, destination_root: Rails::Command.root endさて、ここから沼に入っていきます。しばらく自分がどこにいるか分からなくなります。ちょっと最後の方まで書いてたんですが説明するのがめんどくさくなったので、興味のある方は覗いてみてください。 Rails って大きいなあ(小並)ってのがひしひしと分かります。
要するに Rails コマンドとそれを実行するファイル名は対応していて、 rails generate なら generate_command.rb、 rails console なら console_command.rb を見に行けばなんとなく中で何をやっているかが分かる、というしくみです。
いつもやっているソースコードリーディングの思考をそのまま書いたみたいな感じでまとまりないのは申し訳ないですが、備忘録程度に書いてるので、まあそんなもんだと思ってください。Thor について(おまけ)
なお、各コマンドのオプションについては、 class_option で大半を定義しています。これは Thor の機能で http://whatisthor.com/#class-options 、Thor クラスを継承したクラスにこのメソッドを書くと、クラス全体で定義しておきたいオプションを設定することができる、というものです。
たとえば、
sample.rb#!/usr/bin/env ruby require 'thor' class Nya < Thor class_option :nyanchu, type: :boolean, default: false desc 'hello NAME', 'say hello to NAME' def hello(name) if options[:nyanchu] puts "#{name}, nyanchu~!" else puts "#{name}, nya~!" end end end Nya.start(ARGV)としておくと、勝手に
[--nyanchu], [--no-nyanchu]
オプションをつけてくれるようになります。コマンド名を指定せずにファイルを実行すると、よく見かける README を出力してくれます。なにこれ便利。root@10161f5ac926:/hoge# ./bin/sample Commands: sample hello NAME # say hello to NAME sample help [COMMAND] # Describe available commands or one specific command Options: [--nyanchu], [--no-nyanchu]なので、このオプションにしたがって以下のように実行してみると、ちゃんと引数を解釈してくれます。
root@10161f5ac926:/hoge# ./bin/sample hello Waku --nyanchu Waku, nyanchu~!もっと詳しく Thor について知りたい方は公式ドキュメントか GitHub のリポジトリを見に行っても良いかもしれません。
http://whatisthor.com/
https://github.com/erikhuda/thor