20191024のRailsに関する記事は18件です。

Railsのテストについて

テストの種類

全体的なテスト
・システムテスト:ブラウザを通してアプリケーションの挙動を外部的に確認できるテスト
・結合テスト:いろいろな機能の連続を確認するテスト
・機能テスト:コントローラ単位のテスト

個々の部品のテスト(モデル・ルーティング・ビュー・ヘルパー・メーラー・ジョブ)

テストの頻度

モデルのテスト(高頻度)

結合テスト(高頻度)

ルーティング・メーラー・ジョブのテスト(モデルよりは頻度が高くない)

テストを行うために必要なもの

・データベース(自分で準備する)
・テストデータ(自動的に作られる)

RSpecとCapybaraを使ってテストを行う

Unitテスト(ユニットテスト・単体テスト)

class 〇〇Test < ActiveSupport::TestCase
 test  "test name" do
    #実行するコード
   assertion #ここにassertionメソッドを #結果の確認 #testブロックの中に最低一つは必要
 end
end

testは

test name do
実行するコード
確認用のassertionメソッド
end

で行う

Integrationテスト(統合テスト)

複数のコントローラーに跨って、ユーザーの実際の操作を追跡するような用途で利用する。
→多段階のプロセスの追跡が可能

作り方(Integrationテストは自分で作る必要がある)

rails generate integration_test testname

テストの準備と後始末(テストスクリプトでの予約メソッド)

setup (使用するリソースの初期化)
teardown(使用したリソースの後始末)
これらは基底クラスで定義されているので、個別にオーバーライドして使用する

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

【Rails】mysqlオプションを付けた新たなアプリケーション作成時のエラー対処(Mac)

//////////////////////////////////////////

MacOS Mojave ver.10.14.6
Ruby 2.5.3p105
Rails 6.0.0

//////////////////////////////////////////

はじめに

railsプロジェクト作成の際に、
railsインストールしてrails newをしたところエラーが発生しました。
苦戦したため、備忘のため記録残します。
※悪戦苦闘しながら急ぎ作成したため少し伝わりにくいところもあるかと思います。
時間のあるときに修正を加えていきたいと思います。

対処方法(要約)

今回の経験とネット情報をまとめますと、以下の対処が良いかと思います。
試してみてください。

・権限付加を問われたら

$ sudo chown -R [ユーザ名]:staff /Users/[ユーザ名]/.rbenv

・ERROR: Error installing mysql2 みたいなのが出たら

$  bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"

※それでもダメなら、再インストール

起こったこと(長いです)

「blog」という名前のファイルをmysqlオプション付きで作成するため、以下のコマンドを実行

$ rails _6.0.0_ new blog -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 /Users/xxx/projects/blog/.git/
      create  package.json
      create  app
      create  app/assets/config/manifest.js
      create  app/assets/stylesheets/application.css
      create  app/channels/application_cable/channel.rb
      create  app/channels/application_cable/connection.rb
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/javascript/channels/consumer.js
      create  app/javascript/channels/index.js
      create  app/javascript/packs/application.js
      create  app/jobs/application_job.rb
      create  app/mailers/application_mailer.rb
      create  app/models/application_record.rb
      create  app/views/layouts/application.html.erb
      create  app/views/layouts/mailer.html.erb
      create  app/views/layouts/mailer.text.erb
      create  app/assets/images/.keep
      create  app/controllers/concerns/.keep
      create  app/models/concerns/.keep
      create  bin
      create  bin/rails
      create  bin/rake
      create  bin/setup
      create  bin/yarn
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/cable.yml
      create  config/puma.rb
      create  config/spring.rb
      create  config/storage.yml
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/application_controller_renderer.rb
      create  config/initializers/assets.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/content_security_policy.rb
      create  config/initializers/cookies_serializer.rb
      create  config/initializers/cors.rb
      create  config/initializers/filter_parameter_logging.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/new_framework_defaults_6_0.rb
      create  config/initializers/wrap_parameters.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/master.key
      append  .gitignore
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  lib
      create  lib/tasks
      create  lib/tasks/.keep
      create  lib/assets
      create  lib/assets/.keep
      create  log
      create  log/.keep
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/apple-touch-icon-precomposed.png
      create  public/apple-touch-icon.png
      create  public/favicon.ico
      create  public/robots.txt
      create  tmp
      create  tmp/.keep
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor
      create  vendor/.keep
      create  test/fixtures
      create  test/fixtures/.keep
      create  test/fixtures/files
      create  test/fixtures/files/.keep
      create  test/controllers
      create  test/controllers/.keep
      create  test/mailers
      create  test/mailers/.keep
      create  test/models
      create  test/models/.keep
      create  test/helpers
      create  test/helpers/.keep
      create  test/integration
      create  test/integration/.keep
      create  test/channels/application_cable/connection_test.rb
      create  test/test_helper.rb
      create  test/system
      create  test/system/.keep
      create  test/application_system_test_case.rb
      create  storage
      create  storage/.keep
      create  tmp/storage
      create  tmp/storage/.keep
      remove  config/initializers/cors.rb
      remove  config/initializers/new_framework_defaults_6_0.rb
         run  bundle install
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 13.0.0
Using concurrent-ruby 1.1.5
Using i18n 1.7.0
Using minitest 5.12.2
Using thread_safe 0.3.6
Using tzinfo 1.2.5
Using zeitwerk 2.2.0
Using activesupport 6.0.0
Using builder 3.2.3
Using erubi 1.9.0
Using mini_portile2 2.4.0
Using nokogiri 1.10.4
Using rails-dom-testing 2.0.3
Using crass 1.0.5
Using loofah 2.3.1
Using rails-html-sanitizer 1.3.0
Using actionview 6.0.0
Using rack 2.0.7
Using rack-test 1.1.0
Using actionpack 6.0.0
Using nio4r 2.5.2
Using websocket-extensions 0.1.4
Using websocket-driver 0.7.1
Using actioncable 6.0.0
Using globalid 0.4.2
Using activejob 6.0.0
Using activemodel 6.0.0
Using activerecord 6.0.0
Using mimemagic 0.3.3
Using marcel 0.3.3
Using activestorage 6.0.0
Using mini_mime 1.0.2
Using mail 2.7.1
Using actionmailbox 6.0.0
Using actionmailer 6.0.0
Using actiontext 6.0.0
Using public_suffix 4.0.1
Using addressable 2.7.0
Fetching bindex 0.8.1
Installing bindex 0.8.1 with native extensions
Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/bindex-0.8.1
An error occurred while installing bindex (0.8.1), and Bundler
cannot continue.
Make sure that `gem install bindex -v '0.8.1' --source 'https://rubygems.org/'`
succeeds before bundling.

In Gemfile:
  web-console was resolved to 4.0.1, which depends on
    bindex
         run  bundle binstubs bundler
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your
Gemfile.
         run  bundle exec spring binstub --all
bundler: command not found: spring
Install missing gem executables with `bundle install`
       rails  webpacker:install
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.

エラー部分から和訳すると

ネイティブ拡張を使用したbindex 0.8.1のインストール
Errno :: EACCES:許可が拒否されました

bindex(0.8.1)およびBundlerのインストール中にエラーが発生したため続行できません。

bundle installする前に`gem install bindex -v '0.8.1' --source 'https://rubygems.org/'`がうまくいくか確認してください。

Gemfile:
web-consoleは4.0.1に解決されました。

bindex
bundlerで一連のGemのBinstubのインストールを実行します。
Gemfileにリストされているgemソースのいずれにもgem 'mysql2(> = 0.4.4)'が見つかりませんでした。

bundle exec spring binstub --allを実行します
Bundler:springのコマンドが見つかりません

`bundle install`で見つからないgem実行可能ファイルをインストールします
rails webpacker:install
Gemfileにリストされているgemソースのいずれにもgem 'mysql2(> = 0.4.4)'が見つかりませんでした。

`bundle install`を実行して、欠落しているgemをインストールします。

と言った感じです。
※間違ってたらご指摘ください。

まずは、最初の指示通り

$  gem install bindex -v '0.8.1'

を実施。
すると、

Building native extensions. This could take a while...
ERROR:  While executing gem ... (Errno::EACCES)
    Permission denied @ dir_s_mkdir - /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/bindex-0.8.1

まさかのエラー。。。
和訳すると

ネイティブ拡張の構築。 これにはしばらく時間がかかる可能性があります...
エラー:gemの実行中...(Errno :: EACCES)
     許可が拒否されました@ dir_s_mkdir-/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/bindex-0.8.1v

なぜ、許可されない。。。
権限の問題か?
ということで、root権限でコマンドを実行することに

$  sudo gem install bindex -v '0.8.1'

すると
「Password:」とパスワードを要求されるので」、
自分のパソコンのパスワードを入力

すると

Building native extensions. This could take a while...
Successfully installed bindex-0.8.1
Parsing documentation for bindex-0.8.1
Installing ri documentation for bindex-0.8.1
Done installing documentation for bindex after 0 seconds
1 gem installed

が表示!!
お、お?! 激しく動揺

手が震えてタイピングが、、、
Google先生、和訳をお願いします!!

command + C
command + V

ネイティブ拡張の構築。 これにはしばらく時間がかかる可能性があります...
bindex-0.8.1が正常にインストールされました
bindex-0.8.1の解析ドキュメント
bindex-0.8.1のRIドキュメントのインストール
0秒後にbindexのドキュメントのインストールを完了しました
1つのgemがインストールされました

インストール成功!!

さっそく、確認!!

$ cd blog  (blogはファイル名です)
$ bundle install

どうだ!

Fetching msgpack 1.3.1
Installing msgpack 1.3.1 with native extensions
Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/msgpack-1.3.1
An error occurred while installing msgpack (1.3.1), and Bundler
cannot continue.
Make sure that `gem install msgpack -v '1.3.1' --source 'https://rubygems.org/'`
succeeds before bundling.

In Gemfile:
  bootsnap was resolved to 1.4.5, which depends on
    msgpack

なんと、別のエラー。。。

が、しかし
指示通り実行するだけだ!

$  sudo gem install msgpack -v '1.3.1'

※またしても許可されなかったため、root権限で実行してます

Building native extensions. This could take a while...
Successfully installed msgpack-1.3.1
Parsing documentation for msgpack-1.3.1
Installing ri documentation for msgpack-1.3.1
Done installing documentation for msgpack after 0 seconds
1 gem installed

OK!
さっそく、確認!!

$ bundle install
Fetching bootsnap 1.4.5
Installing bootsnap 1.4.5 with native extensions
Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/bootsnap-1.4.5
An error occurred while installing bootsnap (1.4.5), and Bundler
cannot continue.
Make sure that `gem install bootsnap -v '1.4.5' --source
'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  bootsnap

はい次!

$ sudo gem install bootsnap -v '1.4.5'
Building native extensions. This could take a while...
Successfully installed bootsnap-1.4.5
Parsing documentation for bootsnap-1.4.5
Installing ri documentation for bootsnap-1.4.5
Done installing documentation for bootsnap after 0 seconds
1 gem installed

はい確認!!

$ bundle install
Fetching byebug 11.0.1
Installing byebug 11.0.1 with native extensions
Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/byebug-11.0.1
An error occurred while installing byebug (11.0.1), and Bundler
cannot continue.
Make sure that `gem install byebug -v '11.0.1' --source 'https://rubygems.org/'`
succeeds before bundling.

In Gemfile:
  byebug

はい次!

$ sudo gem install byebug -v '11.0.1'
Building native extensions. This could take a while...
Successfully installed byebug-11.0.1
Parsing documentation for byebug-11.0.1
Installing ri documentation for byebug-11.0.1
Done installing documentation for byebug after 12 seconds
1 gem installed

はい確認!!

$ bundle install
Fetching ffi 1.11.1
Installing ffi 1.11.1 with native extensions
Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/ffi-1.11.1
An error occurred while installing ffi (1.11.1), and Bundler cannot
continue.
Make sure that `gem install ffi -v '1.11.1' --source 'https://rubygems.org/'`
succeeds before bundling.

In Gemfile:
  spring-watcher-listen was resolved to 2.0.1, which depends on
    listen was resolved to 3.1.5, which depends on
      rb-inotify was resolved to 0.10.0, which depends on
        ffi

はい次!

$ sudo gem install ffi -v '1.11.1'
Building native extensions. This could take a while...
Successfully installed ffi-1.11.1
Parsing documentation for ffi-1.11.1
Installing ri documentation for ffi-1.11.1
Done installing documentation for ffi after 19 seconds
1 gem installed

はい確認!!

$ bundle install
Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.2
An error occurred while installing mysql2 (0.5.2), and Bundler
cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'`
succeeds before bundling.

In Gemfile:
  mysql2

はい次!

$ sudo gem install mysql2 -v '0.5.2'
Building native extensions. This could take a while...
ERROR:  Error installing mysql2:
    ERROR: Failed to build gem native extension.

    current directory: /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2/ext/mysql2
/Users/xxx/.rbenv/versions/2.5.3/bin/ruby -I /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/site_ruby/2.5.0 -r ./siteconf20191024-7974-ialg44.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/opt/mysql@5.6/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... no
checking for MYSQL_OPT_SSL_ENFORCE in mysql.h... no
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... yes
-----
Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/opt/mysql@5.6/lib
-----
creating Makefile

current directory: /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2/ext/mysql2
make "DESTDIR="
compiling client.c
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2 for inspection.
Results logged to /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.2/gem_make.out

またエラー;
ひとまず順に和訳する。

ネイティブ拡張の構築。 これにはしばらく時間がかかる可能性があります...
エラー:mysql2のインストールエラー:
     エラー:gemネイティブ拡張のビルドに失敗しました。
~
-----
/usr/local/opt/mysql@5.6/bin/mysql_configでmysql_configを使用する
-----
~
-----
MySQLライブラリがパスされずmysql2がロードされませんない場合、システムにrpathを設定する方法がわからない
-----
-----
libpathを/usr/local/opt/mysql@5.6/libに設定する
-----
Makefileの作成

現在のディレクトリ:/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2/ext/mysql2
「DESTDIR =」をクリーンにする

~

clang:エラー:リンカコマンドが終了コード1で失敗しました(呼び出しを確認するには-vを使用してください)

make:*** [mysql2.bundle]エラー1

失敗、終了コード2

Gemファイルは、検査のために
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2
にインストールされたままになります。

結果のログ
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.2/gem_make.out

とりあえずわかるのは
mysql2で何かしらのエラー

これ、StackOverFlowにて対策が投稿されていたので、参照してみる
https://stackoverflow.com/questions/30834421/error-when-trying-to-install-app-with-mysql2-gem

For anybody still experiencing the issue:

When you install openssl via brew, you should get the following message:

Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries

Generally there are no consequences of this for you. If you build your own software and it requires this formula, you'll need to add to your build variables:

LDFLAGS: -L/usr/local/opt/openssl/lib
CPPFLAGS: -I/usr/local/opt/openssl/include
PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig

You can set these build flags (for the local application) by running the following:



bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"

和訳すると

まだ問題が発生している場合:

brew経由でopensslをインストールすると、次のメッセージが表示されます。

Appleは、独自のTLSおよび暗号ライブラリを支持して、OpenSSLの使用を廃止しました

通常、これによる影響はありません。 
独自のソフトウェアをビルドし、この式が必要な場合は、ビルド変数に追加する必要があります。

LDFLAGS:-L / usr / local / opt / openssl / lib
CPPFLAGS:-I / usr / local / opt / openssl / include
PKG_CONFIG_PATH:/ usr / local / opt / openssl / lib / pkgconfig

以下を実行して、これらのビルドフラグを設定できます(ローカルアプリケーション用)。

bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"

ということで、以下を実行!!

$  bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"
You are replacing the current local value of build.mysql2, which is currently nil

和訳する!

現在nilであるbuild.mysql2の現在のローカル値を置き換えています

何か実行はされたみたい!
いけたのか?

さっそく確認!!

$ bundle install
Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.2
An error occurred while installing mysql2 (0.5.2), and Bundler
cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'`
succeeds before bundling.

In Gemfile:
  mysql2

同じエラー!!
変わってないやん!!

本来はこの辺りでうまくいくらしいが、どうやら最新のVerはそんな生ぬるくないらしい、、、

もうこうなったら
アンインストールして、再インストールしかない!!
※手順はこちらを参照させていただきました
https://qiita.com/akiko-pusu/items/aef52b723da2cb5dc596

そして再インストールを実行!

Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Errno::EACCES: Permission denied @ rb_sysopen -
/Users/maedamasaki/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2/CHANGELOG.md
An error occurred while installing mysql2 (0.5.2), and Bundler
cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source
'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2

変わらず!!
でも、ここで指摘されているのは結局権限!!

こちらのサイト (http://infinity108.com/114/) を参照させていただくと
どうやら

$ sudo gem install

に問題があるみたい。
そこで、今度は以下の方法で権限を付加します。

$ sudo chown -R [ユーザ名]:staff /Users/[ユーザ名]/.rbenv

↓↓↓

$  sudo chown -R xxx ~/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2

$  sudo chown -R xxx ~/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.2

そして確認!!

$ bundle install
Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Fetching puma 3.12.1
Installing puma 3.12.1 with native extensions
Errno::EACCES: Permission denied @ dir_s_mkdir - /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/puma-3.12.1
An error occurred while installing puma (3.12.1), and Bundler cannot continue.
Make sure that `gem install puma -v '3.12.1' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  puma

おー、違うエラーが!!

$ sudo chown -R xxx ~/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/puma-3.12.1
chown: /Users/xxx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/puma-3.12.1: No such file or directory

と出たので、以下でチャレンジ!

$ sudo chown -R xxx ~/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static

よし!
さっそく確認!

$ bundle install
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 13.0.0
Using concurrent-ruby 1.1.5
Using i18n 1.7.0
Using minitest 5.12.2
Using thread_safe 0.3.6
Using tzinfo 1.2.5
Using zeitwerk 2.2.0
Using activesupport 6.0.0
Using builder 3.2.3
Using erubi 1.9.0
Using mini_portile2 2.4.0
Using nokogiri 1.10.4
Using rails-dom-testing 2.0.3
Using crass 1.0.5
Using loofah 2.3.1
Using rails-html-sanitizer 1.3.0
Using actionview 6.0.0
Using rack 2.0.7
Using rack-test 1.1.0
Using actionpack 6.0.0
Using nio4r 2.5.2
Using websocket-extensions 0.1.4
Using websocket-driver 0.7.1
Using actioncable 6.0.0
Using globalid 0.4.2
Using activejob 6.0.0
Using activemodel 6.0.0
Using activerecord 6.0.0
Using mimemagic 0.3.3
Using marcel 0.3.3
Using activestorage 6.0.0
Using mini_mime 1.0.2
Using mail 2.7.1
Using actionmailbox 6.0.0
Using actionmailer 6.0.0
Using actiontext 6.0.0
Using public_suffix 4.0.1
Using addressable 2.7.0
Using bindex 0.8.1
Using msgpack 1.3.1
Using bootsnap 1.4.5
Using bundler 2.0.2
Using byebug 11.0.1
Using regexp_parser 1.6.0
Using xpath 3.2.0
Using capybara 3.29.0
Using childprocess 3.0.0
Using ffi 1.11.1
Using jbuilder 2.9.1
Using rb-fsevent 0.10.3
Using rb-inotify 0.10.0
Using ruby_dep 1.5.0
Using listen 3.1.5
Using method_source 0.9.2
Using mysql2 0.5.2
Fetching puma 3.12.1
Installing puma 3.12.1 with native extensions
Fetching rack-proxy 0.6.5
Installing rack-proxy 0.6.5
Using thor 0.20.3
Using railties 6.0.0
Using sprockets 3.7.2
Using sprockets-rails 3.2.1
Using rails 6.0.0
Fetching rubyzip 2.0.0
Installing rubyzip 2.0.0
Fetching sass-listen 4.0.0
Installing sass-listen 4.0.0
Fetching sass 3.7.4
Installing sass 3.7.4
Fetching tilt 2.0.10
Installing tilt 2.0.10
Fetching sass-rails 5.1.0
Installing sass-rails 5.1.0
Fetching selenium-webdriver 3.142.6
Installing selenium-webdriver 3.142.6
Fetching spring 2.1.0

ついに完了!!!!

長かった!!!

メモ

※with native extensions
gemの中には、ruby以外の言語(ネイティブ:C、C++)に依存しているものもあり、そのような依存関係のあるものをインストールする場合に表示される。
https://kossy-web-engineer.hatenablog.com/entry/2019/01/23/202225

※bundler, bundle install
https://www.sejuku.net/blog/19426#bundler

※root権限(sudo)
https://www.atmarkit.co.jp/ait/articles/1611/28/news036.html

※その他、参考になりそうなリンクも念の為貼っておきます。勉強します。
・gem installでpermission deniedされました
 https://qiita.com/tokimari/items/feda1ed61f2d8b5b317c

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

Rails チュートリアル 6章 独学学習メモ

こちらは『Railsチュートリアル』の独学学習のメモになります。

環境について
- Macを使用
- cloud9を利用

第6章 ユーザーのモデルを作成する

6.1 Userモデル

Railsでは、データを永遠に保存する方法として、データベースを使用します。
RailsライブラリのActive Recordを使うことによって簡単に操作することが可能になります。また、Rubyでデータに関することが定義ができるようになります。

6.1.1 データベースの移行

Railsは、データ保存をするときに、リレーショナルデータベースを使います。各行は、それぞれデータ属性のカラムを持ちます。

まず、ユーザーコントローラを作っていきます。

rails g controller Users new

その後、Userモデルを作っていきます。
nameや emailの属性を使ったUserモデルを使っていきます。

rails generate model User name:string email:string

※コントローラ名は、複数形。モデル名は、単数形。モデルのテーブル名は、複数形です。

name:stringやemail:stringオプションのパラメータを指定することで、属性を指定することができます。

generateコマンドでマイグレーションファイルが作成されます。
このファイルは、Rubyでデータベースについての変更の命令を定義し、その後、その命令はActiveRecordによって翻訳されて、SQLとなり実行されます。

そして、ファイル名前に作成された時間のタイムスタンプがつきます。それは、同じファイルが作られないようにするためです。つまり、同じファイル名の競合を防ぐためです。

そして、マイグレーションを実行します。

rails db:migrate

実行するとこのようなファイルが作成されます。
db/development.sqlite3
このファイルをそのまま開こうとすると、全く読めない文字列が出てきますが、DB Browser for SQLiteを使うと簡単にデータベースの構造を確認できます。

マイグレーションは、元に戻すことも可能です。

rails db:rollback

6.1.2 modelファイル

生成された、UserモデルのファイルはApplicationRecordを継承しています。

6.1.3 ユーザーオブジェクトを作成する

railsコンソールを起動します。

rails console --sandbox

--sandboxとは、コンソール終了時に、このコンソール起動時のデータベース変更を取り消してくれます。

User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

ユーザークラスを作成することなく、ユーザオブジェクトを作成することができます。しかし、このままでは、中身がnilになってしまっています。

引数を渡して、ユーザークラスを作成するとこのようになります。

user = User.new(name: "aaa", email: "bbb@example.com")

ユーザークラスが有効か確かめることができます。

>> user.valid?
true

ユーザーオブジェクトを保存します。

user.save
..
..
..
=>ture

ユーザーオブジェクトを呼び出してみます。

user
=>#<User id: 1,name: "aaa", email: "bbb@example.com", .....>

ユーザークラスにアクセスします。

>> user.name
=> "aaa"
>> user.email
=> "bbb"

② ①の方法だと2段階に分けなければなりません。しかし、このようにすることで1段階にすることが可能です。

User.create(name: "zzz", email: "yyy@example.org")

 gold = User.create(name: "gold", email: "gold@example.com")
  1. ユーザーオブジェクトを削除する方法
gold.destroy

gold  #メモリ上には、まだ保存されています。
=><User id: 3, name: "gold", email: "gold@example.com", ...... >

6.1.4 ユーザーオブジェクトを検索する

  • id3のユーザーが削除されているか確かめます。
User.find(3)
ActiveRecord::RecordNotFound: Couldn't find User with ID=3

データベースには、id3のユーザーは、削除されているので、見つかりません。

  • find_by
User.find_by(email: "bbb@example.com")

このemailに該当するユーザーが検索されます。

  • first
User.first

最初のユーザーが表示されます。

  • all

User.all

全てのユーザーが表示されます。

6.1.5 ユーザーオブジェクトを更新する

  • ユーザーオブジェクトを更新する方法。
>> user.email = "silver@example.net"
=> "silver@example.net"

>> user.save
=> true

user.reload.email  
=> "gold@example.net"
#saveしないでリロードするとsave前に戻ってしまいます。
  • update_attributesメソッド

更新と保存を一度にする方法。
また、検証を回避する方法。
特定の事項を変更したいときに例外的に使用します。

>> user.update_attributes(name: "silver", email: "silver@example.org")
=> true

6.2 ユーザーを検証する

Userモデルに、nameとemail属性が与えられましたが、これらの属性に制限をかけるために検証を学習していきます。

6.2.1 有効性を検証する

有効なモデルの検証方法は、まず、有効にモデルオブジェクトを作成し、その属性のうちの1つを有効出ない属性に意図的に変更し、それをちゃんと失敗するかを確かめます。

Userモデルを作成されたこのtest/models/user_test.rbファイルを編集していきます。

  • setupメソッド

このメソッドは、このメソッド内に書かれた処理は、各テストが作動する前に、実行されます。

また、setupメソッド内でインスタンス変数の@Userを宣言することで全てのテスト内で、@Userが使えるようになります。

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  • validメソッド

@Userにvalidメソッドを使うことによって有効性を確かめることができます。

  test "should be valid" do
    assert @user.valid?
  end

6.2.2 存在性を検証する

  • presentメソッド

属性が存在するかを検証するメソッドです。

test/models/user_test.rb
  test "name should be present" do
    @user.name = "     "
    assert_not @user.valid?
  end

assert_notメソッドを使い一旦、Userオブジェクトが有効でない状態にします。

app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
end

さらにvalidatesメソッドにpresence:trueという引数を与えてます。

コンソールで確かめてみます。

>> u = User.new(name: "", email: "gold@example.com")
>> u.valid?
=> false
>> u.errors.full_messages #どこがエラーしたのか確認が可能
=> ["Name can't be blank"]

ちなみに、全てのバリデーションに通った時に、tureを返します。

emailも同様の方法で確かめることができます。

6.2.3 長さを検証する

Userの名前やemailの長さに制限をかけます。
名前は、50文字。emailは、255文字が通例となっています。

test/models/user_test.rb
  test "name should not be too long" do
    @user.name = "a" * 51
    assert_not @user.valid?
  end

 #"a" * 51とは、aが51個という意味。aを51個書かなくてよくするために。

  test "email should not be too long" do
    @user.email = "a" * 244 + "@example.com"
    assert_not @user.valid?
  end

nameとemailに引数を渡す。

app/models/user.rb
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }

6.2.4 フォーマットを検証する

今の所、emailの制限は、最大文字制限でした。
さらに、emailに正しい制限をかけていきます。
例えば、無効なemailアドレスが書かれているかということです。

test/models/user_test.rb
# ①有効なアドレスを検証する
  test "email validation should accept valid addresses" do
    valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
                         first.last@foo.jp alice+bob@baz.cn]
    valid_addresses.each do |valid_address|
      @user.email = valid_address
      assert @user.valid?, "#{valid_address.inspect} should be valid"
    end
  end

# ②無効なアドレスを検証する
  test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                           foo@bar_baz.com foo@bar+baz.com]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  end

"#{valid_address.inspect} should be valid"この引数は、どのメールアドレスが無効だったかを教えてくれます。

そして、正規表現の定数を作り、formatオプションの引数に正規表現の引数を渡すことで有効なアドレスかを確かめることができます。

app/models/user.rb
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX }

6.2.5  一意性を検証する

ユーザーの一意性を確保するために、validatesメソッドの:uniqueオプションを使っていきます。

まず、そのためには、重複させたメールアドレスが必要になります。
その後、@user.saveした後では、同じアドレスがあるため、無効となります。

test/models/user_test.rb
  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email addresses should be unique" do
    duplicate_user = @user.dup
    duplicate_user.email = @user.email.upcase
    @user.save
    assert_not duplicate_user.valid?
  end

上記のテストを通過させるために、uniqueness: trueというオプションを追加します。しかし、そのオプションでは、大文字と小文字の区別がされないため、uniqueness: { case_sensitive: false }を追加する必要があります。

app/models/user.rb
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
 end

この状態では、同時刻に、複数の同じメールアドレスが登録されてしまうと片方だけでなく両方のアドレスが登録されてしまいます。

解決方法は、データベース側で一意性を強制することでできます。

データベース上のemailカラムにインデックスを追加します。

rails generate migration add_index_to_users_email

Railsのメソッドのadd_indexでemailカラムを追加しています。
そこにunique: trueを指定することで一意性を強制することができるようになります。

db/migrate/[timestamp]_add_index_to_users_email.rb
  def change
    add_index :users, :email, unique: true
  end

テストDBのサンプルデータが含まれています。fixturestest/fixtures/users.ymlこのファイル内で一意性が保たれてないため、テストがエラーになってしまいます。
中身を全て削除します。

その後、マイグレート(rails db:migrate)をおそらく忘れたため、エラーが発生しました。

$ rails t
Migrations are pending. To resolve this issue, run:
        bin/rails db:migrate RAILS_ENV=test
$ bin/rails db:migrate RAILS_ENV=test
== 20191023112923 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
   -> 0.0019s== 20191023112923 AddIndexToUsersEmail: migrated (0.0026s) ====================

$ rails t
Running via Spring preloader in process 7137
Started with run options --seed 36960

  14/14: [============================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.63485s
14 tests, 30 assertions, 0 failures, 0 errors, 0 skips

さらに、メールアドレスの一意性を確保するために、データベースに保存する前に全て、小文字にしてしまいます。このようにすることによって、一意性がさらに確保されます。

app/models/user.rb
  before_save { self.email = email.downcase }
  #self.email = self.email.downcaseの略
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }

emailを小文字に対応するかテストを書いていきます。

test/models/user_test.rb
  test "email addresses should be saved as lower-case" do
    mixed_case_email = "Foo@ExAMPle.CoM"
    @user.email = mixed_case_email
    @user.save
    assert_equal mixed_case_email.downcase, @user.reload.email
  end

6.3 セキュアなパスワードを追加する

セキュアなパスワードとは、各ユーザーにパスワードの入力の確認をさせ、そのパスワードをハッシュ化し、データベースに保存することを言います。

ハッシュ化とは、ハッシュ関数を使って、入力されたパスワードを元に戻せない文字列に変更してしまうことです。

6.3.1 ハッシュ化されたパスワード

モデルの中に、password_digestという属性が含まれていれば、セキュアなパスワードを生成するhas_secure_passwordメソッドを使うをことができます。

$ rails generate migration add_password_digest_to_users password_digest:string

add_password_digest_to_usersのto_usersをすることで、usersテーブルにカラムを追加するマイグレーションがrailsによって、自動で生成されます。
さらに、password_digest:stringを付け加えることで、マイグレーションにパスワードを作っていくということをrailsに伝えられます。

db/migrate/[timestamp]_add_password_digest_to_users.rb
  def change
    add_column :users, :password_digest, :string
  end

usersテーブルpassword_digestカラムを追加していきます。

その後$ rails db:migrate
を実行します。

そして、has_secure_passwordを使うには、最先端のハッシュ関数であるbcryptが必要になります。そのために、Gemfileを編集し、bundle installを実行します。

6.3.2 ユーザーがセキュアなパスワードを持っている

app/models/user.rb
  .
  .
  has_secure_password
end

has_secure_passwordを追加します。

そして、テストを実行するも失敗します。
理由は、has_secure_passwordによって、password属性とpassword_confirmation属性に対して、自動でバリデーションが追加されているためです。

バリデーションがあるのにも関わらず、test/models/user_test.rbのファイルの@userには、passwordとpassword_confirmationの値が書いてないからテストがエラーになってしまいます。

test/models/user_test.rb
  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "aaa", password_confirmation: "aaa")
  end

6.3.3 パスワードの最小文字数

パスワードが6文字以上や空のパスワードが設定できないように、バリデーションをかけていきます。

test/models/user_test.rb
  test "password should be present (nonblank)" do
    @user.password = @user.password_confirmation = " " * 6
    assert_not @user.valid?
  end

  test "password should have a minimum length" do
    @user.password = @user.password_confirmation = "a" * 5
    assert_not @user.valid?
  end

パスワードの更新に備えて、バリデーションを作成していきます。

app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  .
  .
  .
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }
end

6.3.4 ユーザーの作成と認証

railsコンソールで実際にユーザーを作成して、試していきます。

$ rails console
>> User.create(name: "nihontaro", email: "nihon@example.com",
?>             password: "nihon", password_confirmation: "nihon")

DB Browser for SQLiteを使って、usersテーブルの中身を確認して、ユーザーが正しく保存できているか確認してみます。パスワードの中身がハッシュ化されています。

has_secure_passwordをUserモデルに追加したため、authenticateメソッドが使えるようになっています。

user.authenticate("japan")
=>false
user.authenticate("nihon")
=> #<User id: 1, name: "nihontaro", email: "nihon@example.com",
created_at: ..., updated_at: .....,
password_digest: ... >
!!user.authenticate("nihon")
=> true

6.4 最後に

マージまでし、herokuにプッシュまでします。

本番環境でも、Userモデルを使うためには、heroku runをする必要があります。

$ heroku run rails db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails WebSocket と ActionCableを用いたリアルタイム通信

なぜやろうと思ったか

railsで非同期通信を行った時に、自分のwebブラウザしか結果が反映されない問題を解消したかった。
前まで、setIntervalで10秒ごとに読み込みさせて認識させていたんですが、変化がない時にも動くので何か他の方法がないかなぁ〜と模索していました。

完成コード

tableはmessage,column名はbodyでdbを作成

channels/chat_message_channel.rb
class ChatMessageChannel < ApplicationCable::Channel
  def subscribed;stream_from "chat_message_channel";end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak(data)
    @message = Message.create(body: data["message"])
    data['id'] = @message.id
    ActionCable.server.broadcast 'chat_message_channel',message: data
  end
end
message.js
App.chatmessage = App.cable.subscriptions.create("ChatMessageChannel", {
  connected: function() {
    // Called when the subscription is ready for use on the server
  },

  disconnected: function() {
    // Called when the subscription has been terminated by the server
  },

  received: function(data) {
    let message_id = data['message']['id']
    let message_text = data['message']['message']
    let html =`
      <tr>
      <td><a href="/messages/${message_id}">Show</td>
      <td><a href="/messages/${message_id}/edit">Edit</td>
      <td><a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/messages/${message_id}">Destroy</td>
      <td>${message_text}</td>
      </tr>`
        $("#message").append(html)
    // Called when there's incoming data on the websocket for this channel
  },

  speak: function(message) {
    return this.perform('speak',{message: message});
  }
  }, $(document).on('keypress', '[data-behavior~=speak_chat_messages]', function(event) {
  if (event.keyCode === 13){
   App.chatmessage.speak(event.target.value)
    event.target.value = ''
    event.preventDefault()

  }
}));

rails g scaffoldで作成したindex.html.erbにidなど少し組み込んだ

index.html.erb
<p id="notice"><%= notice %></p>

<h1>Messages</h1>

<table>
  <thead>
    <tr>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody id="message">
    <% @messages.each do |message| %>
      <tr>
        <td><%= link_to 'Show', message %></td>
        <td><%= link_to 'Edit', edit_message_path(message) %></td>
        <td><%= link_to 'Destroy', message, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      <td><%= message.body %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>
<%= link_to 'New Message', new_message_path%>

</div>
<br/>
<div class="aaa"><%= @message.body %></div>
<form>
  <label>
    websocketsで送信: <input type="text" data-behavior="speak_chat_messages">
  </label>
</form>

javascripts/cable.js
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
//
//= require action_cable
//= require_self
//= require_tree ./channels

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);

主にこの4つのfileをいじってもらいことになりますね。

cable.js

まず最初に読まれるfileです。
クライアントからサーバーに対してWebSocket接続しているコードで、
App.cable = ActionCable.createConsumer();でWebSocket通信を確立している。
他のサイトにはcoffee fileを呼ぶものがありますが、今回はmessage.jsに記述を書いていきます。

message.js

App.chatmessage = App.cable.subscriptions.create("ChatMessageChannel", {
でcable.jsからmessage.jsに接続している。
成功すれば、connectedに飛び、失敗したもしくは拒否した場合はdisconnectedに飛ぶ。

その後、inputのkeypress functionを動かす。
enterキーを押すと,if (event.keyCode === 13)の条件がtrueになる。

keycodeとは?
http://shanabrian.com/web/javascript/keycode.php
キーボードを押すと、キーコードが発行される。enterは13番。
用いればどのキーボードを押したかがわかる。

preventDefaultでrailsのhttp通信を中断し、その代わりにjqueryのApp.chatmessage.speak functionを実行する。引数にevent.target.value(
keypressしたinputの中身)を定義。

次に、speak: function(message) が呼ばれます。
return this.performでサーバーのChat_message_Channel#speakを呼び出す。
仮に、perform('appear',message: message)なら Chat_message_Channel#appearを呼び出す。
第二引数には渡したい値をハッシュで渡してあげます。
{performはrailsのメソッドだと思われ、サブスクリプションでperformメソッドを使って、RPC(リモートプロシージャコール)として利用できるみたい}
よくわかりません。。。。(・・;)
https://railsguides.jp/action_cable_overview.html

Chat_message_channel.rb

speak actionが呼ばれました。ここでは、railsのcontrollerの役割を担う部分になります。
情報をdbにsaveしたりできます。
speak(data)におけるdataの中身はハッシュで受け取ってます。params的な感じで。
data=> {"message"=>"111111", "action"=>"speak"}
今回は、saveした@message.idをviewで使用するので、dataのハッシュにidを入れ込みます。
この時点でのdataはこんな感じ。
data=> {"message"=>"111111", "action"=>"speak", "id"=>125}


 speakメソッドのActionCable.server.broadcastでは第1引数にチャネル名、第2引数に発言メッセージを指定することで、サーバー側からChatMessageChannelにWebSocketで接続している全クライアントに対して発言メッセージを配信することが可能になります。
これで、全クライアントに対して処理を行う準備ができました。
メッセージを配信したので、受け取りを行わなければなりませんね。

message.js

message.jsに戻ってきました。
配信されたdataをここのreceived functionで受け取ります。
message: {message: "1111", action: "speak", id: 128}
__proto__: Object

dataはこんな感じなので取り出し方は、data['message']['id']みたいな感じ。
その後、作成したhtmlを追加する処理を書いて終了となります。。

最後に

参考にした記事にはcoffee fileを用いて使う手法もありましたが、利便性が悪いのでjqueryを使用してやってみました。
これで、リアルタイムのchatが作れるよ〜〜〜。
完成動作

参照した記事

https://railsguides.jp/action_cable_overview.html
https://codezine.jp/article/detail/10153?p=1
https://qiita.com/fujisawaryohei/items/3a567a64691276da0d45

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

bundle installでnokogiriがインストールされない時

bundle install --path vendor/bundle したら怒られた

Installing nokogiri 1.10.1 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/t.riku/rikuproject/vendor/bundle/ruby/2.4.0/gems/nokogiri-1.10.1/ext/nokogiri
/Users/t.riku/.rbenv/versions/2.4.7/bin/ruby -r ./siteconf20191024-82200-mdp5kr.rb extconf.rb --use-system-libraries
checking if the C compiler accepts  -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  

--------------------省略--------------------

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

  /Users/t.riku/rikuprojects/vendor/bundle/ruby/2.4.0/extensions/x86_64-darwin-18/2.4.0-static/nokogiri-1.10.1/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /Users/t.riku/rikuproject/vendor/bundle/ruby/2.4.0/gems/nokogiri-1.10.1 for inspection.
Results logged to /Users/t.riku/rikuprojects/vendor/bundle/ruby/2.4.0/extensions/x86_64-darwin-18/2.4.0-static/nokogiri-1.10.1/gem_make.out

An error occurred while installing nokogiri (1.10.1), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.10.1' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  awesome_nested_fields was resolved to 0.6.4, which depends on
    rails was resolved to 4.2.11.1, which depends on
      actionmailer was resolved to 4.2.11.1, which depends on
        actionpack was resolved to 4.2.11.1, which depends on
          actionview was resolved to 4.2.11.1, which depends on
            rails-dom-testing was resolved to 1.0.9, which depends on
              nokogiri

解決策

/Users/t.riku/rikuprojects/vendor/bundle/ruby/2.4.0/extensions/x86_64-darwin-18/2.4.0-static/nokogiri-1.10.1/mkmf.log

のなかに入っているログを見てみる

mkmf.log
clang -o conftest -I/Users/t.riku/.rbenv/versions/2.4.7/include/ruby-2.4.0/x86_64-darwin18 -I/Users/t.riku/.rbenv/versions/2.4.7/include/ruby-2.4.0/ruby/backward -I/Users/t.riku/.rbenv/versions/2.4.7/include/ruby-2.4.0 -I. -I/Users/t.riku/.rbenv/versions/2.4.7/include  -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT    -O3 -Wno-error=shorten-64-to-32  -pipe  -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 conftest.c  -L. -L/Users/t.riku/.rbenv/versions/2.4.7/lib -L. -L/Users/t.riku/.rbenv/versions/2.4.7/lib  -fstack-protector -L/usr/local/lib     -lruby-static -framework Foundation  -lpthread -ldl -lobjc  "
ld: malformed file
/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/lib/libpthread.tbd:4:18: error: unknown enumerated scalar
platform:

file '/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/lib/libpthread.tbd'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
checked program was:

* begin */
1: #include "ruby.h"
2:
3: int main(int argc, char **argv)
4: {
5:   return 0;
6: }
/* end */

うーん、なんか分からんけどxcodeのバージョンに問題があるのかな。
https://www.famlog.jp/article/1180
↑これ見る感じ、Mojava使ってる人は'/Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /'のパッケージをインストールすれば行けるらしい。

あんま、わかってないけどとりあえずやってみる。

sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /

いざ

$ bundle install --path vendor/bundle

--------------------省略--------------------

Bundle complete! 62 Gemfile dependencies, 195 gems now installed.
Bundled gems are installed into `./vendor/bundle`

通ったー。

全然理解してないので、わかったら詳細追記します。

参考にした記事

https://github.com/esnme/ultrajson/issues/332
https://www.famlog.jp/article/1180

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

Rails 基礎入門と基本コマンド

はじめに

備忘録として書いています。
おかしなところはバンバン教えていただけたらと思います


$ rails new sample_app
訳:「sample_appと言う名前で新しくファイル一式作ってやるぜ!」

$ rails s
訳:「railsを起動するぜ!sはserverのsだぜ!」

$ rails g controller home top
訳:
app/views/の中にhomeというフォルダを、その中にtopってファイル作るぜ!
app/controllers/の中にhome_controller.rbというコントローラー作るぜ!
app/config/の中にroutes.rbっていうルーティングファイル作るぜ!
app/assets/stylesheetsの中にhome.scssってファイル作るぜ!
後注意なんだけど$ rails g controller home ~っていうコマンドもう使えないからね、だってもうhomeは存在してるわけだから。だからこれからは自分でファイル作成してコントローラーとルーティングファイルに追記してね。」

app/controllers/home_controller.rb
class HomeController < ApplicationController
  def top #topのことをアクション名と言う
  end
end
#homeの中のtopと言う名前のファイルを呼び出している
app/config/routes.rb
Rails.application.routes.draw do
  get "home/top" => "home#top"
end
#get "URL" => "コントローラー名#アクション名(つまりファイル名)"

つまり
app/views/home/の中のファイル名app/controllers/home_controller.rbアクション名

基本viewで表示する変数はコントローラーの中に書く

app/controllers/home_controller.rb
class HomeController < ApplicationController
  def top 
    @hoge1 = "ほげほげ"
  end
end

データベースとの連携

前提

$ rails g posts indexを実行したとする


$ rails g model Post content:text
訳:
db/migrate/の中にpostsテーブルを作るぜ!
contentってのはカラム名で
textってのはデータの型だ!
app/models/の中にpost.rbってファイル作るぜ!

$ rails db:migrate
訳:
データーベースの変更内容を保存するぜ!
変更加えたときは必ず実行してくれよな!

$ rails console
訳:
データベースの中にばんばん情報入れてくだろ?
このコマンドを使うんだ!

$ rails console
> post1 = Post.new(content: "ほげほげほげー!")

#訳: post1と言う変数名で"Post"というDBの中にnewメソッドを使ってデータを作成するぜ!
     contentというカラムに”ほげほげほげー!”という内容を入れるぜ!

> post.save 

#訳: セーブするぜ!

> posts = Post.first
#訳: dbの一番上の情報だけを取得するぜ!

・全部のときはposts = Post.all
・インデックス番号0番目のcontentというカラムの中身だけ欲しいときはPost.all[0].content


posts_controller.rb
def index
  @post1 = Post.all
end

訳: postsテーブルのデータを全て取得してくるぜ!

app/views/posts/index.html.erb
<% @post.each do |post| %>
  <div class="aaaa">
    <%= post1.content%>
  </div>
<% end %>

訳: 取得したデータを1つずつ繰り返し表示していくぜ!

> post = Post.find_by(id:2)

routes.rb
get "posts/:id" => "posts#show"

とすることでURLは格納されたidをそのまま使える
posts#showは投稿の詳細を表示する。

あと忘れちゃいけないのがコントローラーファイル

get "posts/:id" => "posts#show"に対応するのが以下の内容

post_controller.rb
def show
  @id = params[:id]
end

このparamsとはパラメータのことで
サービスの利用者(ユーザー)がサーバー対して送ってきた情報を呼び出す

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

Railsチュートリアル 第7章 ユーザー登録 - ユーザー登録成功

ユーザー登録に成功した画面のモックアップ

ユーザー登録に失敗した場合の挙動は、ここまでの学習で実装が完了しました。

ユーザー登録に成功した画面のモックアップ - ユーザー登録に成功したら、当該リンク先のような画面を出力するように実装していきます。

登録フォームの完成

現状、有効な情報で登録操作を行うと何が起こるか

現状では、ユーザー登録フォームに有効な情報で登録操作を行っても、元の画面に戻されてしまいます。

スクリーンショット 2019-10-22 18.29.55.png

このときのrails serverのログ情報は以下のようになります。

Started POST "/users" for 172.17.0.1 at 2019-10-22 09:28:55 +0000
...略
No template found for UsersController#create, rendering head :no_content
Completed 204 No Content in 1605ms (ActiveRecord: 50.4ms)

「No template found for UsersController#create」というのがポイントですね。実際に発生しているのは以下の事態です。

  1. Railsはデフォルトのアクションに対応するビューを表示しようとする
  2. createアクションに対応するビューのテンプレートは現状存在しない
  3. 元の画面に戻されてしまう

登録内容はRDBにきちんと反映される

なお、現状においても、有効な情報で登録操作を行えば、登録内容はRDBにきちんと反映されます。

Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foobar Foobar", "email"=>"foobar@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.1ms)  begin transaction
  User Exists (6.7ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "foobar@example.com"], ["LIMIT", 1]]
  SQL (25.7ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?)  [["name", "Foobar Foobar"], ["email", "foobar@example.com"], ["created_at", "2019-10-22 09:28:55.485707"], ["updated_at", "2019-10-22 09:28:55.485707"], ["password_digest", "$2a$10$qgI/adIo1AfEBIYOxU636uh/k2kEWw9IM2F/E5kdqMAdkTgeNtRV."]]
   (18.0ms)  commit transaction
# rails console
Running via Spring preloader in process 505
Loading development environment (Rails 5.1.6)
>> User.count
   (3.0ms)  SELECT COUNT(*) FROM "users"
=> 2
>> User.find(2)
  User Load (4.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
=> #<User id: 2, name: "Foobar Foobar", email: "foobar@example.com", created_at: "2019-10-22 09:28:55", updated_at: "2019-10-22 09:28:55", password_digest: "$2a$10$qgI/adIo1AfEBIYOxU636uh/k2kEWw9IM2F/E5kdqMA...">

別のページへのリダイレクトを実装する

Railsの一般的な慣習では、「ユーザー登録に成功した場合、ページを描画せずに別のページにリダイレクトする」ことになっています。今回は、「ユーザー登録に成功した場合、新しく作成されたユーザーのプロフィールページにリダイレクトする」という動作にしましょう。

変更するファイルはapp/controllers/users_controller.rbです。

app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
-       # TODO: 保存の成功をここに実装する
+       redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end

redirect_to @userというコードについて

redirect_to @user

上記のコードと下記のコードは等価の挙動となります。

redirect_to user_url(@user)

上述2つのコードを等価とみなす動作も、Railsの機能の一つです。

演習 - 登録フォームの完成

1. 有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。

有効な情報を送信した際、rails serverのログは以下のようになります。

Started POST "/users" for 172.17.0.1 at 2019-10-22 09:46:59 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foo Bar Baz", "email"=>"foobar.foobar@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.2ms)  begin transaction
  User Exists (5.8ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "foobar.foobar@example.com"], ["LIMIT", 1]]
  SQL (12.4ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?)  [["name", "Foo Bar Baz"], ["email", "foobar.foobar@example.com"], ["created_at", "2019-10-22 09:46:59.624267"], ["updated_at", "2019-10-22 09:46:59.624267"], ["password_digest", "$2a$10$aVxeV/ey4bi5F9/DnuXRd.M0/41r/X3Sj7rl1MdKnpubgKUni9tlu"]]
   (18.4ms)  commit transaction
Redirected to http://localhost:8080/users/3
Completed 302 Found in 237ms (ActiveRecord: 48.6ms)

RDBに対するINSERT文が発行された後、確かにリダイレクト処理が行われていますね。POSTリクエストの処理結果は、最終的に「302 found」となっています。

Started GET "/users/3" for 172.17.0.1 at 2019-10-22 09:46:59 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
  Parameters: {"id"=>"3"}
  User Load (3.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
  Rendered users/show.html.erb within layouts/application (3.0ms)
  Rendered layouts/_rails_default.erb (276.7ms)
  Rendered layouts/_shim.html.erb (0.4ms)
  Rendered layouts/_header.html.erb (0.8ms)
  Rendered layouts/_footer.html.erb (3.6ms)
Completed 200 OK in 509ms (Views: 444.4ms | ActiveRecord: 3.4ms)

最終的には、以下のような画面が表示されます。

スクリーンショット 2019-10-22 18.47.20.png

先ほど作成したユーザー情報のemail属性の値が"foobar.foobar@example.com"であることを踏まえて、rails consoleで、実際にRDBにユーザー情報が保存されていることを確認してみましょう。

# rails console

>> User.find_by(email: "foobar.foobar@example.com")
  User Load (11.1ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "foobar.foobar@example.com"], ["LIMIT", 1]]
=> #<User id: 3, name: "Foo Bar Baz", email: "foobar.foobar@example.com", created_at: "2019-10-22 09:46:59", updated_at: "2019-10-22 09:46:59", password_digest: "$2a$10$aVxeV/ey4bi5F9/DnuXRd.M0/41r/X3Sj7rl1MdKnpu...">

確かに、実際にRDBにユーザー情報が保存されています。

2. リスト 7.28を更新し、redirect_to user_url(@user)redirect_to @userが同じ結果になることを確認してみましょう。

app/controllers/users_controller.rbを変更します。

app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
-       redirect_to @user
+       redirect_to user_url(@user)
      else
        render 'new'
      end
    end

    ...略
  end

以上の変更を行った上で、有効な情報を送信すると、rails serverのログは以下のようになります。

Started POST "/users" for 172.17.0.1 at 2019-10-22 10:47:07 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"YZ6q9xae3E23E8J5R3JNqhrVZ5r+jL9UV+QIZy7LiF/sdbfCNkYFsMUr+8tqltfCwiHus2I/viw+wT92e7nluQ==", "user"=>{"name"=>"Foo Bar Foobar", "email"=>"foobar.baz@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.1ms)  begin transaction
  User Exists (6.0ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "foobar.baz@example.com"], ["LIMIT", 1]]
  SQL (20.5ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?)  [["name", "Foo Bar Foobar"], ["email", "foobar.baz@example.com"], ["created_at", "2019-10-22 10:47:07.789313"], ["updated_at", "2019-10-22 10:47:07.789313"], ["password_digest", "$2a$10$frfU4tFYiTifJHRfHLQ7nO.3c1ju83yJsQbklZU7pGJa2LNdXSENG"]]
   (16.6ms)  commit transaction
Redirected to http://localhost:8080/users/4
Completed 302 Found in 258ms (ActiveRecord: 55.5ms)


Started GET "/users/4" for 172.17.0.1 at 2019-10-22 10:47:07 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
  Parameters: {"id"=>"4"}
  User Load (5.6ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
  Rendered users/show.html.erb within layouts/application (0.9ms)
  Rendered layouts/_rails_default.erb (262.1ms)
  Rendered layouts/_shim.html.erb (0.3ms)
  Rendered layouts/_header.html.erb (0.7ms)
  Rendered layouts/_footer.html.erb (0.5ms)
Completed 200 OK in 516ms (Views: 472.0ms | ActiveRecord: 5.6ms)

flash

flashとその実装

世界中に多数存在するWebアプリケーションの多くには、「新規ユーザー登録が完了した後に表示されるページにメッセージを表示し、2度目以降のログインではそのページにメッセージを表示しない」という機能が実装されています。

Railsでそのような情報を表示するには、flashという特殊な変数を使用します。その用法はRubyのハッシュと類似しています。ここでは、Railsの一般的な慣習に従い、:successというキーには成功時のメッセージを代入するように実装します。

flashを実装する箇所は、app/controllers/users_controller.rb内です。

app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
+       flash[:success] = "Welcome to the Sample App!"
        redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end

flash内に存在するキーがあるかを調べ、もしあればキーに対応する値(メッセージ)を全て表示する

今回はこの項見出しに示した機能を実装していきます。そういえば、第4章中のサンプルプログラムで、「ブロックを使い、特定のハッシュのkeyとvalueの組を順次表示していく」というものがありました。

>> flash = { success: "It worked!", danger: "It failed." }
>> flash.each do |key, value|
?>   puts "#{key}"
>>   puts "#{value}"
>> end
success
It worked!
danger
It failed.
=> {:success=>"It worked!", :danger=>"It failed."}

ここでflashという変数名を使ったのは、今回の実装に向けての伏線だったのですね。

というわけで、flash変数の内容をWebサイト全体にわたって表示できるようにするための(仮)コードは以下のようになります。

<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>

(仮)とあるのは、このコードは「HTMLとERbが雑に混ざっており、リファクタリングの余地がある」ためです。Railsチュートリアル本文には、「これをキレイに整形する作業は演習として残しておきましょう」とあります。

alert-<%= message_type %>なるクラスの意味

上述「flash変数の内容をWebサイト全体にわたって表示できるようにするための(仮)コード」の中には、以下のようなコードがあります。

alert-<%= message_type %>

これは、「埋め込みRubyによって、適用するCSSのクラスをメッセージの種類により変更する」というコードです。例えば、:successキーのメッセージが表示される場合であれば、適用されるCSSクラスは以下のようになります。

alert-success

:successはシンボルであるが、テンプレートに反映させる際には、埋め込みRubyが自動的に"success"という文字列に変換している」という点に注意が必要です。

このような実装により、以下の挙動を実現することができます。

  • キーの内容により、異なったCSSクラスを適用させる
    • 結果として、メッセージの種類によってスタイルを動的に変更させることができる
    • 例えば、「ログインに失敗した旨を示すメッセージをflash[:danger]で表示し、その際に自動的にalert-dangerというクラスを適用させる」など

Bootstrap CSSにおける、alert-から始まる4つのクラス

BootstrapのCSSでは、alert-から始まるクラスが以下4つ定義されています。

  • alert-success
  • alert-info
  • alert-warning
  • alert-danger

全体としてどういう挙動になるか

flash[:success] = "Welcome to the Sample App!"
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>

上述のコードを踏まえると、当該部分に対して最終的に出力されるHTMLは以下のようになります。

<div class="alert alert-success">Welcome to the Sample App!</div>

flash変数の内容をWebサイトのレイアウトに追加する

app/views/layouts/application.html.erb
  <!DOCTYPE html>
  <html>
    ...略
    <body>
      <%= render 'layouts/header' %>
      <div class="container">
+       <% flash.each do |message_type, message| %>
+         <div class="alert alert-<%= message_type %>"><%= message %></div>
+       <% end %>
        <%= yield %>
        <%= render 'layouts/footer' %>
        <%= debug(params) if Rails.env.development? %>
      </div>
    </body>
  </html>

演習 - flash

1. コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう。例えば"#{:success}"といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。

# rails console --sandbox

>> "#{:success}"
=> "success"
>> "#{:danger}"
=> "danger"

2. 先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。

前提として、以下のようなコードを考えます。

<% flash = { success: "It worked!", danger: "It failed." } %>
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>

最終的に出力されるHTMLは以下のようになるはずです。

<div class="alert alert-success">It worked!</div>
<div class="alert alert-danger">It failed.</div>

実際のユーザー登録

データベースの内容をリセットする

RailsのActive Recordのマイグレーション機能により、データベースの内容をリセットすることが可能です。データベースの内容をリセットするためのコマンドは、rails db:migrate:resetです。実行すると、以下のようなログと共に、データベースの内容がリセットされます。

# rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20190928080951 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0119s
== 20190928080951 CreateUsers: migrated (0.0122s) =============================

== 20191010034159 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
   -> 0.0133s
== 20191010034159 AddIndexToUsersEmail: migrated (0.0136s) ====================

== 20191013040411 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
   -> 0.0134s
== 20191013040411 AddPasswordDigestToUsers: migrated (0.0137s) ================

ログを見るに、以下のような動作が行われているようですね。

  • 既存テーブルの削除
  • 新規テーブルの作成
    • 既存のマイグレーションに基づき、列情報も自動生成される
  • インデックスの定義
  • パスワードダイジェスト列の生成

Railsチュートリアルにおける今回の事例は、「Usersコントローラのuser.saveコマンドが実行された回数が学習者ごとに異なる可能性があり、RDBの内容の違いを吸収するためにデータベースの内容をリセットする」という趣旨のものです。

有効なユーザー情報を登録する

早速、ユーザー登録フォームに有効なユーザー情報を入力し、最初のユーザーを作成してみましょう。Railsチュートリアル本文にならい、以下の内容でユーザーを作成してみます。

スクリーンショット 2019-10-23 7.47.16.png

上記はユーザー情報作成完了時の画面です。ユーザー登録の成功を示すフラッシュメッセージもきちんと表示されていますね。なお、Bootstrap CSSにおいて、successクラスの背景色・文字色は、上記スクリーンショットのように爽やかな緑色です。

スクリーンショット 2019-10-23 7.49.54.png

ユーザー情報ページを再読み込みすると、今後はフラッシュメッセージは表示されなくなります。上記スクリーンショットは、再読み込み時のものです。

演習 - 実際のユーザー登録

1. Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。

# rails console

>> User.count
   (3.4ms)  SELECT COUNT(*) FROM "users"
=> 1

>> User.find_by(email: "example@railstutorial.org")
  User Load (3.9ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "example@railstutorial.org"], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2019-10-22 22:46:59", updated_at: "2019-10-22 22:46:59", password_digest: "$2a$10$j21OfGX82PY0/BqDcapJmeeo/xaVKgSQ9pEZD8hAp4B...">

RDBにも新しく作成されたユーザーの情報が正しく反映されているようです。

2. 自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。

まず、ユーザー登録フォームの[Create my account]ボタンをクリックしたところからのrails serverのログを示します。

Started POST "/signup" for 172.17.0.1 at 2019-10-22 22:55:21 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"iAAnpc1ZKq9VuKfKkDNV0dVc4M8reSqR57fJU+5zPp8o8FsU4a2WCdXm++5dmoqwagJOnOJWHnu3mt+IIQCS1Q==", "user"=>{"name"=>"Hoge Hoge", "email"=>"[Gravatar登録済みの自分のメールアドレス]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.1ms)  begin transaction
  User Exists (7.4ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "[Gravatar登録済みの自分のメールアドレス]"], ["LIMIT", 1]]
  SQL (20.8ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?)  [["name", "Hoge Hoge"], ["email", "[Gravatar登録済みの自分のメールアドレス]"], ["created_at", "2019-10-22 22:55:21.433059"], ["updated_at", "2019-10-22 22:55:21.433059"], ["password_digest", "$2a$10$GnOotTswgiIehevLmmlDV.XvhMgBXN67XAZp2KvTxip9WtDVsyo0."]]
   (23.3ms)  commit transaction
Redirected to http://localhost:8080/users/2
Completed 302 Found in 143ms (ActiveRecord: 51.7ms)

最終的にはどうなるでしょうか。

スクリーンショット 2019-10-23 7.55.39.png

上記スクリーンショットは、ユーザー登録が正常終了した直後のものです。Gravatarに登録したプロフィール画像がきちんと表示されています。

成功時のテスト

今度は「ユーザー登録フォームにおいて、有効なユーザー情報が送信された場合」に対するテストを書いていきます。今回の主題は「データベースの内容が正しいかを検証することにより、有効なユーザー情報に対して正しくユーザーが作成されたことを確認する」というものです。

assert_differenceメソッド

ユーザー登録失敗時のテストに登場したassert_no_differenceと対になるメソッドです。メソッドの使い方はassert_differenceと同様です。

  • 第1引数に文字列をとる
    • 今回の事例では、'User.count'を第1引数とする
  • 第2引数には、比較した結果の差異をとる
    • 今回は1とする
  • ブロックをとり、ブロックの実行前と実行後で第1引数の文字列が指す要素の値を比較する
assert_difference 'User.count', 1 do
  post users_path, ...
end

成功時のテストのコード

test/integration/users_signup_test.rb
  class UsersSignupTest < ActionDispatch::IntegrationTest

    ...略
+
+   test "valid signup information" do
+     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!
+     assert_template 'users/show'
+   end
  end

この時点でテストは通ります。

# rails test integrate
Running via Spring preloader in process 137
Started with run options --seed 37813

  21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.48632s
21 tests, 49 assertions, 0 failures, 0 errors, 0 skips

follow_redirect!について

follow_redirect!
assert_template 'users/show'

assert_differenceブロックの後に、follow_redirect!というコードがあります。

follow_redirect!は、「リダイレクトを伴うテストにおいて、リダイレクト先の処理を明示的に実行する」というメソッドです。今回は、「POSTリクエストの実行結果がリダイレクトになる1ので、users/showテンプレートが表示されている」目的でfollow_redirect!メソッドを用いています。

!がつくだけに、follow_redirectというメソッドも存在すると考えられます。Google検索等してみたのですが、なぜ!がつくかについての情報は見つけられませんでした。ただ、「follow_redirectというメソッドは、Rails 1.1.6から2.1.0まで存在したものの、以降廃止されて現在に至っている」のだそうです。

users/showテンプレートが正しく表示されることの確認

assert_template 'users/show'

users/showテンプレートが正しく表示されるには、以下の条件が全て満足される事が必要になります。

  • Userのルーティングが正しく実装されている
  • Userのshowアクションが正しく動いている
  • show.html.erbビューが正しく実装されている

テストの全体像まとめ

今回実装したテストは、以下の事柄について確認しています。

  • 正しいユーザー登録データに対し、postメソッドが正しく動作する
  • ユーザー登録の内容がRDBに反映される
    • ユーザー登録後、登録前に比べてRDBのレコード数が1つ増えているかどうか
  • POSTメソッド完了時のリダイレクトが正しく実装されている
  • users/showテンプレートが正しく表示される

「ユーザー登録成功」というフローで必要とされる機能がカバーされていますね。こういうときに統合テストは便利なのです。

演習 - 成功時のテスト

1. 7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。

ちなみに、テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。

Railsチュートリアル本文にならい、flashが空でないかをテストするだけの実装とします。

test/integration/users_signup_test.rb
  require 'test_helper'

  class UsersSignupTest < ActionDispatch::IntegrationTest

    ...略

    test "valid signup information" do
      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!
      assert_template 'users/show'
+     assert_not flash.empty?
    end
  end

現時点の状態からapp/controllers/users_controller.rbに手を加えなければ、テストは通ります。

# rails test integrate
Running via Spring preloader in process 189
Started with run options --seed 27734

  21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.08653s
21 tests, 50 assertions, 0 failures, 0 errors, 0 skips

一方、app/controllers/users_controller.rbflash[:success]以下の行をコメントアウトすると、テストが通らなくなります。

app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
-       flash[:success] = "Welcome to the Sample App!"
+       #flash[:success] = "Welcome to the Sample App!"
        redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end
# rails test integrate
Running via Spring preloader in process 202
Started with run options --seed 46053

 FAIL["test_valid_signup_information", UsersSignupTest, 1.8803565000016533]
 test_valid_signup_information#UsersSignupTest (1.88s)
        Expected true to be nil or false
        test/integration/users_signup_test.rb:33:in `block in <class:UsersSignupTest>'

  21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.38272s
21 tests, 50 assertions, 1 failures, 0 errors, 0 skips

2.1. 本文中でも指摘しましたが、flash用のHTML (リスト 7.31) は読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。

なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。

app/views/layouts/application.html.erb
  <!DOCTYPE html>
  <html>
    ...略
    <body>
      ...略
      <div class="container">
        <% flash.each do |message_type, message| %>
-         <div class="alert alert-<%= message_type %>"><%= message %></div>
+         <% content_tag(:div, message, class: "alert alert-#{message_type}") %>
        <% end %>
        ...略
      </div>
    </body>
  </html>

「読みにくい」というか、「RubyのコードとHTMLのコードが混在するのはよろしくない」という事情もあります。

Railsのcontent_tagヘルパーにより、RubyのコードのみでHTMLを構成できるようになりました。

2.2. 変更が終わったらテストスイートを実行し、正常に動作することを確認してください。

# rails test integrate
Running via Spring preloader in process 215
Started with run options --seed 26929

  21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.10806s
21 tests, 50 assertions, 0 failures, 0 errors, 0 skips

問題なくテストが通っていますね。

3. リスト 7.28のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。

app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
      if @user.save
        flash[:success] = "Welcome to the Sample App!"
-       redirect_to @user
+       #redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end

このコードに対するテストの結果は以下のようになります。

# rails test integrate
Running via Spring preloader in process 228
Started with run options --seed 63745

ERROR["test_valid_signup_information", UsersSignupTest, 3.581053300000349]
 test_valid_signup_information#UsersSignupTest (3.58s)
RuntimeError:         RuntimeError: not a redirect! 204 No Content
            test/integration/users_signup_test.rb:31:in `block in <class:UsersSignupTest>'

  21/21: [=================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.66224s
21 tests, 48 assertions, 0 failures, 1 errors, 0 skips

テストが通らなくなりました。「RuntimeError: not a redirect! 204 No Content」というエラーメッセージがポイントですね。

4. リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。

assert_difference 'User.count', 1 do
  #...略
end

@user.saveが実行されない場合、RDBにレコードは追加されません。結果、User.countの値は変わらなくなります。「@user.saveの実行後は、実行前と比較してUser.countの値が1増えていなければならない」という条件に反するので、この時点でテストが失敗となります。

実際にやってみるとどうなるでしょうか。

app/controllers/users_controller.rb
  class UsersController < ApplicationController

    ...略

    def create
      @user = User.new(user_params)
-     if @user.save
+     if false
        flash[:success] = "Welcome to the Sample App!"
        redirect_to @user
      else
        render 'new'
      end
    end

    ...略
  end

テスト結果はこのようになります。

# rails test integrate
Running via Spring preloader in process 241
Started with run options --seed 8272

 FAIL["test_invalid_signup_information", UsersSignupTest, 1.5347172000001592]
 test_invalid_signup_information#UsersSignupTest (1.54s)
        Expected at least 1 element matching "div#error_explanation", found 0..
        Expected 0 to be >= 1.
        test/integration/users_signup_test.rb:15:in `block in <class:UsersSignupTest>'

 FAIL["test_valid_signup_information", UsersSignupTest, 1.5782804999998916]
 test_valid_signup_information#UsersSignupTest (1.58s)
        "User.count" didn't change by 1.
        Expected: 1
          Actual: 0
        test/integration/users_signup_test.rb:25:in `block in <class:UsersSignupTest>'

  21/21: [=================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.10267s
21 tests, 44 assertions, 2 failures, 0 errors, 0 skips

あっ、「Users.errorsに何も代入されないため、newがレンダリングされたときにapp/views/shared/_error_messages.html.erbの内容がレンダリングされない。結果、IDがerror_explanationとなるdiv要素が存在しないため、その点でテストが失敗する」というのもありました。


  1. HTTPのステータスコードは「302 Found」が返ってきます。HTTPの仕様としては「303 See Other」が正しい応答なのだそうですが、後方互換性等の観点から、このような場合には302を返すのがデファクトスタンダードになっているそうです。 

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

テスト環境で<meta name="csrf-token">が消える

meta要素からCSRFトークンを取り出すJavaScriptがあると、テスト環境でページが動かなくなることがあります。テスト環境ではallow_forgery_protectionのデフォルトがfalseで、falseだとmetaが埋め込まれないからです。

config/environments/test.rb
config.action_controller.allow_forgery_protection = false

metaがないせいでテストが落ちるときは、一時的に設定を変更します。

before do
  ActionController::Base.allow_forgery_protection = true
end

after do
  ActionController::Base.allow_forgery_protection = false
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Capybara+ChromeでJavaScriptのエラーを出力

headless Chromeを使ったテストで、JavaScriptのエラーのせいで先に進まないときは、次のコードを埋め込んでコンソールの内容を調べられます。

puts page.driver.browser.manage.logs.get(:browser).collect(&:message)

get(:browser)が返すのは、Selenium::WebDriver::LogEntryオブジェクトの配列です。

JavaScriptのconsole.errorconsole.warnの内容も出力できます。console.logconsole.infoconsole.debugの内容は出せません。

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

[Rails] NameError in MessagesController#index uninitialized constant Message::ImageUploader エラー

目的:
CarrierWave・Imagemagickというgemを使い、画像送信できる機能をつけたい!
しかし標題エラーが出たので解決したい。

結論:rails sでサーバー再立ち上げしたところ解決。
他解決案:
・ターミナル:brew install imagemagickでimagemagickを再インストール→bundle update
・ターミナル:spring stop、 spring startでspring再起動
参考URL: https://qiita.com/yokoyan/items/0f82fc3eede75cd6db55

★下記コード入力後、
```
class Message < ApplicationRecord
belongs_to :group
belongs_to :user
validates :content, presence: true, unless: :image?

mount_uploader :image, ImageUploader
end
```

localhost立ち上げたら、こちらのエラーが出ました。

NameError in MessagesController#index
uninitialized constant Message::ImageUploader

エラー要因:
コードの記述自体はあっていたが、imagemagickがうまくインストールされてなかった。
そこで、再度 brew install imagemagickでインストールし直したが、serverを再度立ち上げてなかったのでその内容が読み込まれてなかった。

学んだこと:
configやgemなどはインストールし直したりすると、その変更内容がサーバー上に反映されてなかったりするので、rails sでサーバー再度立ち上げるのが大事。

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

Rails6で追加されたinsert_allとimport(とその他)のパフォーマンス検証

前回書いた記事で、activerecord-importとRails6で追加されたinsert_allのパフォーマンスを比べたところ、importの方が速そうだったのでもう少しちゃんと検証してみました。

検証方法

速度検証

1,000件のユーザーを様々な方法でバルクインサートします。
1回のバルクインサートだと数msで終わってしまうので、上記を100回実行したときの合計時間で比較します。
計測はbenchmarkを使用します。

メモリ使用量検証

こちらも同様に1,000件のユーザーを様々な方法でバルクインサートします。
計測はmemory_profilerを使用します。

環境

Ruby: 2.6.5
Rails: 6.0.0
rspec-rails: 3.9
factory_bot_rails: 5.1.1

対象

activerecord-import

Railsでバルクインサートできるようにするもっとも有名なgemではないでしょうか。
生成したモデルの配列を渡すだけで簡単にバルクインサートできます。

users = (1..100).map { User.new(name: 'name') }
User.import users

github
https://github.com/zdennis/activerecord-import

insert_all

Rails6.0.0で追加されたバルクインサート用のメソッド。
ハッシュを渡すことで簡単にバルクインサートできます。
importと異なり、created_atとupdated_atを明示的に指定する必要があるので注意が必要です。

users = (1..100).map { { name: 'name', created_at: Time.current, updated_at: Time.current } }
User.insert_all users

doc
https://edgeapi.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-insert_all

素のクエリー

Railsではクエリーをそのまま実行できるのでクエリーを文字列で生成して実行します。

sql = "INSERT INTO users (name, created_at, updated_at) VALUES ('name', NOW(), NOW()),('name', NOW(), NOW())...;"
ActiveRecord::Base.connection.execute sql

検証

実装

benchmarkで検証するbenchmark_bulk_insertとMemoryProfilerで検証するprofiler_bulk_insertを作りました。
純粋にバルクインサートの処理を検証したかったので、データ作成等はBenchmark外で生成しています。

app/models/user.rb
class User < ApplicationRecord
  class << self
    def benchmark_bulk_insert
      # create data
      import_data = []
      1_000.times { import_data << new(name: 'name', created_at: Time.current, updated_at: Time.current) }

      insert_data = []
      1_000.times { insert_data << { name: 'name', created_at: Time.current, updated_at: Time.current } }

      values = []
      1_000.times { values << "('name', '#{Time.current.to_s(:db)}', '#{Time.current.to_s(:db)}')" }
      sql = "INSERT INTO users (name, created_at, updated_at) VALUES #{values.join(',')}"

      require 'benchmark'
      Benchmark.bm 15 do |r|
        transaction do
          r.report 'sql' do
            100.times { bulk_insert_using_sql(sql) }
          end
          raise ActiveRecord::Rollback
        end

        transaction do
          r.report 'insert_all' do
            100.times { bulk_insert_using_insert_all(insert_data) }
          end
          raise ActiveRecord::Rollback
        end

        transaction do
          r.report 'import' do
            100.times { bulk_insert_using_import(import_data) }
          end
          raise ActiveRecord::Rollback
        end
      end
    end

    def profiler_bulk_insert
      # create data
      import_data = []
      1_000.times { import_data << new(name: 'name', created_at: Time.current, updated_at: Time.current) }

      insert_data = []
      1_000.times { insert_data << { name: 'name', created_at: Time.current, updated_at: Time.current } }

      values = []
      1_000.times { values << "('name', '#{Time.current.to_s(:db)}', '#{Time.current.to_s(:db)}')" }
      sql = "INSERT INTO users (name, created_at, updated_at) VALUES #{values.join(',')}"

      p '################# sql ########################'
      transaction do
        report = MemoryProfiler.report do
          bulk_insert_using_sql(sql)
        end
        report.pretty_print(retained_strings: 0, allocated_strings: 100, normalize_paths: true)
        raise ActiveRecord::Rollback
      end

      p '################# insert_all ########################'
      transaction do
        report = MemoryProfiler.report do
          bulk_insert_using_insert_all(insert_data)
        end
        report.pretty_print(retained_strings: 0, allocated_strings: 100, normalize_paths: true)
        raise ActiveRecord::Rollback
      end

      p '################# import ########################'
      transaction do
        report = MemoryProfiler.report do
          bulk_insert_using_import(import_data)
        end
        report.pretty_print(retained_strings: 0, allocated_strings: 100, normalize_paths: true)
        raise ActiveRecord::Rollback
      end
    end

    def bulk_insert_using_import(users)
      import users
    end

    def bulk_insert_using_insert_all(users)
      insert_all users
    end

    def bulk_insert_using_sql(sql)
      connection.execute sql
    end
  end
end

結果

rails consoleで実行しました。

irb(main):080:0> User.benchmark_bulk_insert;nil
                      user     system      total        real
sql               0.000000   0.010000   0.010000 (  0.601744)
insert_all        8.900000   0.030000   8.930000 (  9.951685)
import           10.870000   0.210000  11.080000 ( 12.255004)

irb(main):080:0> User.profiler_bulk_insert
"################# sql ########################"
Total allocated: 4152 bytes (23 objects)
Total retained:  928 bytes (1 objects)

"################# insert_all ########################"
Total allocated: 4493518 bytes (67974 objects)
Total retained:  145088 bytes (2005 objects)

"################# import ########################"
Total allocated: 7187421 bytes (91961 objects)
Total retained:  1824536 bytes (13654 objects)

まず処理時間を見てimportが速いというのは勘違いだったとわかりました。。。ちゃんと調査しないとダメですね。
メモリー使用量を見ると、処理時間と比例して増えています。importでは処理の過程で様々なオブジェクトを生成しているので他の処理より遅いと思われます。
前回の記事でinsert_allの方が遅かったのはbuild_listで生成したオブジェクトを変換するところも計測に入っていたからですね。
そしてimportやinsert_allよりも素のsqlがパフォーマンス最強ということがわかりました。
オブジェクトを生成すればするほど遅くなるので文字列をDBに投げるだけだとかなりパフォーマンスに差がでますね。
素のsqlで実装すると可読性や生産性が落ちるので乱用はしたくないですが、ここぞというときに使うと良さそうです。

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

【Rails】Ransackを使わずにソート機能を実装する

目次

  • 1. 環境
  • 2. 要件
  • 3. カスタムヘルパー実装
    • 3-1. StudentHelper
    • 3-2. ApplicationHelper
  • 4. コントローラー実装
  • 5. ビュー実装
  • 6. 結果(生徒コードでソート)

1. 環境

  • rails 5.2.0
  • ruby 2.6.2
  • MacOS Version 10.14.6

2. 要件

  • 学生テーブル students学生コード (student_code)学生名 (name)学年 (grade_id) でソートしたい。
  • 一覧画面に実装するが、検索フォームがあるのでその結果を保持する。
  • 学生テーブル以外にも今後適用するので、共通処理は纏めておく。

3. カスタムヘルパー実装

3-1. StudentHelper

モデル別にカスタムヘルパーをモジュールを切って実装する。

module StudentHelper
  def student_sort_link(column)
    search_params = search_students_params.empty? ? {} : { search_students_form: search_students_params }
    sort_link(Student.human_attribute_name(column), column, search_params)
  end
end
  • カラム名を引数に取る。
  • 検索パラメータが空の場合、空のハッシュを返す。さもなくばキー値のハッシュを返す
  • 共通処理の sort_link に値を渡す
    • 第一引数: ソート用のリンク文字列。辞書ファイルで日本語に翻訳
    • 第二引数: クエリ文字列 column=[column] を生成する
    • 第三引数: 検索パラメーターのキー値のハッシュ

3-2. ApplicationHelper

共通の処理を記述。

module ApplicationHelper
  def sort_link(column_name, column, **additional_params)
    if params[:column] != column.to_s || params[:direction].nil?
      link_to column_name, { column: column, direction: :asc, **additional_params }
    elsif params[:column] == column.to_s && params[:direction] == 'asc'
      link_to "#{column_name} ▲", { column: column, direction: :desc, **additional_params }
    elsif params[:column] == column.to_s && params[:direction] == 'desc'
      link_to "#{column_name} ▼", { column: column, direction: :asc, **additional_params }
    end
  end
end
  • 第三引数の検索結果のパラメーターは可変長で受け取る
  • 条件分岐は以下の3パターン
    • 現在ソートがかかっているカラムと指定されたカラムが一致しない、もしくは現在ソートがされていない
      • => 指定したカラムで昇順でソートする
    • 現在ソートがかかっているカラムとソート対象のカラムが一致し、なおかつ現在昇順でソートされている
      • => 指定したカラムで降順でソートし、「▲」マークを付ける
    • 現在ソートがかかっているカラムとソート対象のカラムが一致し、なおかつ現在降順でソートされている
      • => 指定したカラムで昇順でソートし、「▼」マークを付ける

4. コントローラー実装

受け取ったクエリ文字列に従ってSQLを走らせる。

def index
  @students = Student.search(@search_form).order(sort_column => sort_direction).preload(:grade)
end

...

private

def sort_column
  params[:column].in?(%w(student_code name grade_id)) ? params[:column] : :id
end

def sort_direction
  params[:direction].in?(%w(asc desc)) ? params[:direction] : :asc
end
  • order は キー => 値 = ソート対象カラム => ソート順 のハッシュを引数にとる
  • sort_column: 指定したカラムがホワイトリストにあればそれをorder のキーに指定し、なければ id を指定する。
  • sort_direction: 指定したソート順がホワイトリストにあればそれをorder の値に指定し、なければ昇順を指定する。

5. ビュー実装

呼び出し元では以下のように引数にシンボルを指定する。

<th class="col-sm-2"><%= student_sort_link(:user_code) %></th>
<th class="col-sm-3"><%= student_sort_link(:name) %></th>
<th class="col-sm-2"><%= student_sort_link(:grade_id) %></th>

6. 結果(生徒コードでソート)

  • デフォルト

クエリ文字列: なし

デフォルト.png

  • 昇順

クエリ文字列: ?column=user_code&direction=asc

昇順.png

  • 降順

クエリ文字列: ?column=user_code&direction=desc

降順.png

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

Rails6 のちょい足しな新機能を試す99(association extension編)

はじめに

Rails 6 に追加された新機能を試す第99段。 今回は、 association extension 編です。
Rails 6 では、 define_extensions で定義される module が model 内の module として定義されるようになっています。
実際の例を見た方がわかりやすいです。

Ruby 2.6.5, Rails 6.0.0 で確認しました。

$ rails --version
Rails 6.0.0

今回は、 Author モデル と Book モデルを定義して、rails console で確認します。
Author は Book を複数持ちます( has_many )。

Rails プロジェクトを作る

$ rails new rails_sandbox
cd rails_sandbox

Author モデルを作る

name を持つ Author モデルを作ります。

$ bin/rails g model Author name

Book モデルを作る

title, published, author_id を持つ Book モデルを作ります。

$ bin/rails g model Book title published:boolean author:references

Author モデルを修正する

Author モデルを修正します。
2つの has_many アソシエーションを定義します。
1つ目の has_many では、 ブロックで ordered メソッドを定義します。
2つ目の scope published_booksextending を使って ordered メソッドを利用できるようにします。
extending の引数が BooksAssociationExtension になっていることに注意してください。

app/models/author.rb
class Author < ApplicationRecord
  has_many :books do
    def ordered
      order(:title)
    end
  end

  has_many :published_books, -> { where(published: true).extending(BooksAssociationExtension) }, class_name: 'Book'
end

seed データを作成する

Author と Book の seed データを作成します。

db/seeds.rb
author = Author.create(
  {
    name: 'Dave Thomas'
  }
)

Book.create(
  [
    { title: 'Programming Ruby', author: author, published: true },
    { title: 'Pragmatic Programmer', author: author, published: true },
    { title: 'Agile Web Development with Rails 6', author: author, published: false }
  ]
)

データベースを作成し、 seed データを登録する

$ bin/rails db:create db:migrate db:seed

rails console で確認する

rails console で確認します。

まずは、1つ目の has_many を確認します。

irb(main):001:0> Author.first.books.ordered
  Author Load (0.3ms)  SELECT "authors".* FROM "authors" ORDER BY "authors"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Book Load (0.3ms)  SELECT "books".* FROM "books" WHERE "books"."author_id" = $1 ORDER BY "books"."title" ASC LIMIT $2  [["author_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Book id: 3, title: "Agile Web Development with Rails 6", published: false, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">, #<Book id: 2, title: "Pragmatic Programmer", published: true, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">, #<Book id: 1, title: "Programming Ruby", published: true, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">]>

2つ目の has_many を確認します。

irb(main):002:0> Author.first.published_books.ordered
  Author Load (0.8ms)  SELECT "authors".* FROM "authors" ORDER BY "authors"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Book Load (0.8ms)  SELECT "books".* FROM "books" WHERE "books"."author_id" = $1 AND "books"."published" = $2 ORDER BY "books"."title" ASC LIMIT $3  [["author_id", 1], ["published", true], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Book id: 2, title: "Pragmatic Programmer", published: true, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">, #<Book id: 1, title: "Programming Ruby", published: true, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">]>

Author::BooksAssociationExtension が定義されていることを確認します。

irb(main):003:0> Author::BooksAssociationExtension
=> Author::BooksAssociationExtension

Rails 5 では

Rails 5.2.3 では、 Author::BooksAssociationExtension ではなく、 AuthorBooksAssociationExtension が定義されます。
以下のように extending の引数を AuthorBooksAssociationExtension と書く必要があります。

app/models/author.rb
class Author < ApplicationRecord
  ...
  has_many :published_books, -> { where(published: true).extending(AuthorBooksAssociationExtension) }, class_name: 'Book'
end

Rails 5.2.3 ではグローバルなモジュールとして定義されるのに対し、Rails 6 では、 Author モデルの中のモジュールとして定義されるので、 Author モデルで利用するときは、Author をつけずに、 BooksAssociationExtension と簡潔に書くことができるようになっています。

試したソース

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

参考情報

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

【開発日記】薬価検索アプリno.3 〜設計・画面の設計〜

前回の内容はこちら。
【開発日記】薬価検索アプリno.2 〜設計・機能の洗い出し〜

開発背景などはこちら
【開発日記】薬価検索アプリno.1 〜開発概要→企画〜

現在の進捗状況

1.必要な機能の洗い出し
2.必要な画面設計 ⬅︎今ココ
3.データベース設計
4.Railsでアプリケーションの雛形作成
5.大きな機能から順に実装
6.テストコードを書いて動作を担保する
7.リファクタリングして整理する
8.デプロイ

画面設計

今日は画面設計をしてみます。
開発するアプリケーションで必要な画面の設計をします。画面がどれだけ必要か、どのような画面にするかをこの段階で決めます。
一応、元ネタというか、参照サイトはこちらです
薬価サーチ

大体は同じように実装してみようかと思います。

ワイヤーフレーム

今回のアプリは画面設計から自分なので、せっかくなのでワイヤーフレームなるものもやってみようかと思います。

ワイヤーフレームを作る際に使えるツールはこちらによくまとまっていますのでどうぞ
Webディレクターにオススメしたい!さくさくワイヤーフレームが作れちゃう便利ツールまとめ

今回は、prottというサービスを使ってみます。
30日間は無料です。

とりあえず作ってみました。

こういうの、考え始めると難しいですね。。。
まずはこの程度はMVPとして作りたい、という目標の意味も込めて。
Image from Gyazo

この辺は少しずつ改善していきます。

prott

無料期間は30日間しかないですが、使い方は本当に簡単で楽なので、本格的にワイヤーフレームを作る必要が出てきたらぜひ使いたいです。

左サイドにはフォームボタンや、パンくず、ページネーションなんかもすでに用意してあって、そのままドラッグすればOK。
Webに合わせたものや、iOSに合わせたものも色々用意してあるので、なんとなく並べるだけでも十分それっぽくなる勢い。
Image from Gyazo

画面右にはプロパティが表示され、文字の大きさ、背景色の変更などが行えます。
Image from Gyazo

まとめ・所感

こういうデザインとかって、サイトの目的や、誰が使うか、何を伝えたいのかなどがはっきりしていないと全く決まらない。
今の開発では既存サイトの改善版を作ろうとしているだけなので、自分で欲しいイメージがある程度はあるけど、全くのゼロからサイトを作ろうとしたら、本当に大変な仕事だと実感。
デザイナーさんありがとうございます。(一緒に働いたことないけど)

最後までおつきあいいただきありがとうございました。

次回、に続きます。

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

【エラー】bundle実行時にmysql2がインストールされない

環境

  • Mac OS
  • Ruby 2.6.3
  • Rails 5.2.3
  • MySQL 8.0

エラー内容

Dockerを使ってRails+MySQLでの環境構築をした後に
bundle installを実行すると

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'`
succeeds before bundling.

といったような
mysql2がインストールできないというエラーが発生しました。

対処方法

$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl/include"
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"

上記二つのコマンドを実行し、
bundlerの設定をするとエラーが解決できて
無事mysql2がインストールできました!

感じたこと

エラー発生時なかなか解決できず困りましたが
とにかく色々と調べた結果上記の対処方法で解決できました!(汗)

Dockerを使って新しく環境構築するとこのエラーが発生するので
環境構築をし終えたら毎度このコマンドを実行してます!

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

Railsのslim導入方法

slimについて

slim
doctype html
html
  head
    title
      | Sample
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
  body
    = yield

上記のような記述方法のRuby制の軽量なテンプレートエンジン。

【1】gemの追加

gem 'slim-rails'
gem 'html2slim'

Gemfileに上記のコードを追加する。

その後、bundle installを実行する。

$ bundle

【2】erbをslimに変換する

$ bundle exec erb2slim app/views/layouts/ --delete

上記のコマンドを打つと
app/views/layouts内のerb形式のファイルがslimに変換される。
以上でslimの導入が完了です。

感じたこと

erbよりも記述量が減り

  • HTMLタグを省略でき効率よく書ける
  • 軽量で動作が速い
  • 全体がすっきりまとまっていてメンテナンスがしやすくなる

など様々なメリットを感じたので積極的に導入してます!!

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

MVCの理解

自分なりに腑に落ちた言い回しと言いますか、そういう物を書いてみます。
※初学者の参考になれば幸いです。

モデル

rails g model cotent:textなどと叩いてテーブルを作るとする。
そのテーブルを扱いたい。どうするのか?
A.テーブルを操作するためのモデルと呼ばれる特殊なクラスを用いて、データベースを使う
※クラスは設計図のようなもの

ビュー

モデルのデータを取り出してユーザが見るのに適した形で表示する要素である。すなわち、UIへの出力を担当する。例えば、ウェブアプリケーションではHTML文書を生成して動的にデータを表示するためのコードなどにあたる。

参照URL
Model View Controller(Wikipedia)

コントローラー

ユーザからの入力(通常イベントとして通知される)をモデルへのメッセージへと変換してモデルに伝える要素である。すなわち、UIからの入力を担当する。モデルに変更を引き起こす場合もあるが、直接に描画を行ったり、モデルの内部データを直接操作したりはしない。

参照URL
Model View Controller(Wikipedia)

余談

コントローラーとビューは早い段階で理解できていましたが、モデルというのが曖昧だったので書いてみました。(というかモデルの事だけを書き留めておこうと思ったら中途半端だったのでVとCについても引用してきただけなのは内緒)
MVCって何かわかる?っていう質問に対しての回答になっていればいいのですが・・・。

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

rake routesによるエラー解決(devise)

rake routesを使ってエラーを解決した事例

タイトル通り、rake routesを使って、エラーを解決した事例を紹介します。
rake routesとは、ターミナルコマンドの一つで、HTTPリクエストの一覧を表示させます。
ざっくり言うと、ビューのリンクがどのコントローラを動かすか、つまりどのビューを表示するかの一覧を表示させることができます。
このコマンドを使うとサクサクエラーが解決することが多々あります。

例えば、devise gemを使ってログイン周りを実装しようとしている時に、サインアップしようとすると下記のようなエラーが出るようになりました。

ActiveRecord::StatementInvalid in Devise::RegistrationsController#create
Mysql2::Error: Field 'name' doesn't have a default value: INSERT INTO `users` (`email`, `encrypted_password`, `created_at`, `updated_at`) VALUES ()

若干読みにくいエラーですが、要するに「nameをテーブルに保存したいのに、nameが入力されてないよ」というエラーですね。
確かにこの時表示されていたサインアップ画面には'name'を入力する箇所がありませんでした(画面無し)
しかし、'name'も入力するようのサインアップ用ビューファイルは用意してあります。
何らかの原因で、このビューファイルが表示されていないようでした。

原因を色々探りましたが、やはりサインアップ画面に移動するためのリンクが上手くできてないのではないかと思い、確認してみました。

※該当箇所

= link_to "Sign up", new_registration_path(resource_name), class: 'btn'

このnew_registration_pathで表示するビュー(動かすコントローラとアクション)を決めています。
それでは、このリンクを実行すると、どのビューファイルを読み込むのかをrake routesで調べてみました。
行数が短くないので、該当箇所だけ載せます。(この記事では、用語などの細かい説明はしません。というか、まだできません)

          Prefix Verb   URI Pattern                          Controller#Action
 new_user_registration GET    /users/sign_up(.:format)             devise/registrations#new

一番右の部分を読めば、どのビューファイルを呼び出そうするのかがわかります。
この場合はdevise/registrations#newによって表示されるビュー、
つまり/Users/****/projects/chat-space/app/views/devise/registrations/new.html.hamlを表示させようとしているけれども、それが上手くいってないことが予想されます。
そこで実際に/Users/****/projects/chat-space/app/views/devise/registrations/new.html.hamlにあるファイルを確認してみました。すると…

そこにビューファイルがありませんでした!

わかってしまえば情けない話なのですが、ビューファイルを違う場所に置いていたのがエラーの原因になったようです。
「該当のビューがないからデフォルトのビューを表示する→そのビューではnameが入力できない(テーブルに送信できない)から上記のようなエラーが出る」というのが、今回のエラーの原因だったようです。

その後、適切なビューファイル(new.html.haml)を適切な場所(/Users/****/projects/chat-space/app/views/devise/registrations/)に配置したことにより、エラーは解決しました。

終わりに

railsでエラーとなると、真っ先にコードの間違いが思い浮かぶと思いますが、今回のようにファイルのディレクトリによるエラーも少なくない回数で起きます。他にも、例えばファイル名をnow.html.hamlとしていても今回のエラーは起きていたはずなので、ファイル名を間違えないことも重要です。
ファイル名やディレクトリについてのエラーは、原因が自力じゃ気づきにくいので、是非ともrake routesをエラー解決の選択肢としてみてください。

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