20191001のRailsに関する記事は17件です。

【Rails】Slim基本文法【初心者】

はじめに

Railsを使った開発で使うことになるであろうSlimについて簡潔にまとめてみました。

Slimとは

Railsで使用できるRuby製のHTMLテンプレートエンジン。

Slimのメリット

HTMLをよりシンプルに見やすく書くことができる。

Slim導入

Slimジェネレータを提供してくれるslim-railsとERB形式のファイルをSlim形式に変換してくれるhtml2slimという2つのGemをインストール

gem 'slim-rails'
gem 'html2slim'

Slimへ変換

すでにあるerb形式のファイルをSlim形式に変換するときは以下のコマンド

$ bundle exec erb2slim 変換したいerbファイルのパス --delete

Slim基本文法

<>が不要、や<% end %>等の閉じタグも不要
<%= %>=
<% %>-
id指定 → #
class指定 → .
コメント → /
改行 → |

ERBとSlimの比較

最初にERB形式

example.html
<div class="contents nav">
  <ul class="navbar-nvv">
    <% if current_user %>
      <li><%= link_to("MyPage", user_path, class: "nav-link") %></li>
    <% else %>
      <li><%= link_to("LogIn", login_path, class: "nav-link") %></li>
    <% end %>
  </ul>
  # これはコメントです
  <p id="greeting">Hello World!!</p>
</div>

次がSlim形式

example.html.slim
.contents.nav
  ul.navbar-nav
    - if current_user
      li= link_to "MyPage", user_path, class: "nav-link"
    - else 
      li= link_to "LogIn", login_path, class: "nav-link"
  / これはコメントです
  p#greeting Hello World!!

さいごに

かなりスッキリしましたね!
以上Slimの基本文法でした。

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

WSL上でRuby on Rails + MySQLの環境づくり

はじめに と 注意

Ruby on Rails + MySQLの環境構築をしていく記事です。

先人様の記事の内容を主に進めていって、エラーが出たとこの解決策を書いてます。

初記事なのでやさしい目で見てください。

背景

Rails + MySQL でアプリ作ってみたいけど環境構築で意味わかんないエラー出ちゃった。

全部Windowsのせいにしよう(逃避)。

でもmac持ってない......WSLならできるかな...?

(ちなみに過去にWSL上で開発環境整えようとして失敗した。)

対象

  • WSL上でRuby on Railsの開発をしたい!
  • SQLiteじゃなくてMySQLでやりたいけど環境構築わかんね!

という方向け。

環境

  • Windows 10 Home 64bit
  • WSL( Windows Subsystem for Linux ) Ubuntu 18.04.3 LTS

1. WSLにRuby on Rails環境を整える

さっそく他人様まかせで申し訳ないですが

とても、とても素晴らしい記事があるので
ほぼこれを参考にやっていきます...

※一部別の方法でインストールする部分があるのでリンク先とこの記事を見比べながら作業してください。
(一個一個のインストールに割と時間かかるので気長に待ちましょう。特にrubyのインストール)

少し変更を加える部分 (と軽いhelp)

「2. Node.jsを入れよう」部分

nodejsを導入する際の

sudo apt-get install npm

というコマンドだとnodeおよびnpmのバージョンが最新ではないためやり方を変更。

間違えて入れちゃったというひとも指示に従ってすれば大丈夫(たぶん)

さあ説明を読むぞって思ったひとにはごめんなさい。この記事が神。

- 続・WSL(Ubuntu)でnode.jsの開発をする準備

さっきのコマンドで入れちゃったって人は「削除する」って項目からやってください。

もう1つ

WSLを再起動とかするとnode -vをしたときにnot found エラーが起きるかも。
そういうときは

export PATH=$HOME/.nodebrew/current/bin:$PATH

を vim かなんかで~/.bashrcに追記して試してみてください。

vim で追記する方法は下の方。

「3. rbenvを入れよう」部分

たいしたことではないですが最後の方で

export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

を ~/.bashrc に追記する(厳密には上の1行はすでに追記されているはず)手順があります。

「どうやって!?」と思った方は下の方に書いてある「.bashrcに追記ってどうするの!!」を見て追記してみてください。

「5. Rubyを入れよう」部分

本当に時間かかります。環境にもよると思うけれど僕は2~30分かかりました(笑)

解決策?

  • 普通は長くて10分くらいらしいのでいったん待ってみる。
  • "rbenv install ruby 遅い"でググって出てきたのを試す

BUILED ERROR!! となる人へ

僕も一度なりました。エラー文中に

Try ~~~ `apt -------` 

といった感じであったので(詳細なエラー文コピー取るの忘れてました;;)、

それに従ってコマンド実行すると治りました。

.bashrcに追記ってどうするの!!

下のコマンドで、vim で .bashrc を開いて一番下とかに追記していく。

vim ~/.bashrc

vim の使い方はこちらでどうぞ

- 知識0から始めるVim講座

2. Ruby on Rails + MySQL

Ruby on Railsの環境構築お疲れさまでした。

僕自身この記事を書きながらやっていたら3時間程度たっていました...笑

ということで次はMySQLです。

MySQLのインストール

まずはMySQLをインストールしていきます。が、ここでもとても良い記事を見つけました。

- WSLのUbuntu 18.04でMySQL 5.7をセットアップ

これ通りに行えばエラーなく行けるはずですが、

過去にMySQLいれたことある人や間違えちゃった人はエラーが起こるかも。(体験談)

僕の場合はWSL上からきれいにMySQLを消してもう一回やればいけました。

過去のMySQL関連のファイルとか消したくないって人以外は下記コマンドを実行しましょう。

sudo apt-get remove --purge mysql-*
sudo apt-get autoremove --purge 
sudo rm -r /etc/mysql
sudo rm -r /var/lib/mysql

MySQLが起動中だとうまく全ファイルを消せないので、

sudo service mysql stop

としてサーバを停止してから行ってください。
またサーバが止まらない!止められない!という方は
MySQLのプロセスを確認して

ps ax | grep mysql

でてきたプロセスを片っ端からkillしていけばサーバが停止します。

sudo kill プロセス番号

Railsと組み合わせる

MySQLのインストールが終わったのでRailsアプリケーションのデータベースをMySQLとして作っていきます。

まずはRailsで使うMySQLのアカウントを作ります!

rootでログインして、

 mysql -u root -p

現在あるアカウントをチェック

 select User,Host from mysql.user;

+------------------+-----------+
| User             | Host      |
+------------------+-----------+
| debian-sys-maint | localhost |
| mysql.session    | localhost |
| mysql.sys        | localhost |
| root             | localhost |
+------------------+-----------+

Rails用のアカウントを作ってチェック

create user 'ユーザネーム'@'localhost' identified by 'パスワード';
select User,Host from mysql.user;

+------------------+-----------+
| User             | Host      |
+------------------+-----------+
| debian-sys-maint | localhost |
| mysql.session    | localhost |
| mysql.sys        | localhost |
| railsuser        | localhost |
| root             | localhost |
+------------------+-----------+

ちゃんと追加されました!

次はRails上でMySQLを使うためのgemをインストールしていきます。

gem install mysql2

しかしここでエラー

 ERROR: Error installing mysql2:
        ERROR: Failed to build gem native extension.
-省略-
mysql client is missing. You may need to 'apt-get install libmysqlclient-dev'  or 'yum install mysql-devel', and try again.
 -省略-

指示に従って、一応sudoをつけて...

 sudo apt-get install libmysqlclient-dev

もう一回..!

 $ gem install mysql2
Building native extensions. This could take a while...

...

Done installing documentation for mysql2 after 0 seconds
1 gem installed

通りました。

次にデータベースをMySQLに指定したRailsアプリケーションを作成。名前は適当。

 rails new mysql-app -d mysql

次にデータベース設定ファイルの書き換えをします。

Ruby on Railsの環境を構築したときにシンボリックリンクを設定したフォルダがあると思うのでそこに先ほど作ったRailsアプリケーションを移動。

最初からそこでrails newすればよかったのでは?

 mv アプリの名前 移動先 -r

vscodeを起動

ファイル > フォルダを開く > Railsアプリ(僕の場合mysql-app)

を選択してvscodeで開く。

config > database.yml を開いて自分の登録したユーザネームとパスワードを入力します。

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: [ユーザネーム]
  password: [パスワード]
  socket: /var/run/mysqld/mysqld.sock

development:
  <<: *default
  database: mysql_app_development

test:
  <<: *default
  database: mysql_app_test

production:
  <<: *default
  database: mysql_app_production
  username: mysql_app
  password: <%= ENV['MYSQL_APP_DATABASE_PASSWORD'] %>

セーブ後データベースを作って

rails db:create

アプリケーションを起動して

rails s

ブラウザ(http://localhost:3000) で確認出来たら終了です!

お疲れさまでした。

a.jpg

参考
- Ruby on RailsでMySQLを使用・接続する際の手順と注意点
- RailsのDBを(初めから| |後から)MySQLに変更する

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

【Rails】画像の表示方法

画像の表示方法でウンウン詰まったのでメモに残しておきます。

方法

2種類あり、それぞれ特徴やパスの書き方が違うみたいです。

1、assets配下に置く場合

格納先:/app/assets/images/

今回はこちらを使用することにしました。
/app/assets/内のファイルはアセットパイプラインの対象となる→結果ページ読み込み速度が早くなる。

※アセットパイプライン
ファイルを最小化(圧縮)して読み込みを早くしてくれるフレームワーク。(初期装備のよう)
アセットを連結することによって、ページリクエスト数を減らすことができる=更にページ速度向上につながるという仕組み。
画像であればブラウザのキャッシュを残すようにしてくれるため、画像の読み込みが早くなる。
ただし、キャッシュを飛ばさないと画像が正しく表示されない、など起きる。
リファレンスの内容が思ったよりあったので、これは今後詳しく学ぶことにします。

書き方

htmlで書く場合
<img src="/assets/img_logo_pc.png" alt="">

「imagesいらないのかーい!」という不意打ちでした。

ヘルパーの場合
<%= image_tag 'img_logo.png' %>

ディレクトリ分けしたくなる気持ちがありましたが、そちらはいったん寝かしておきます。

2、public配下に置く場合

格納先:/public/
ユーザーがアップロードする、などの場合はこちらがいいようです。
こちらは後ほど使用する際にまた追記します。

判断基準

assets=「システム側に依存する画像関係」の場合に使用
public=「ユーザーがアップロードするような画像関係」の場合に使用

とあった。

ざっと調べただけでは明確にわからなかったので、
現状まずは進めつつ今後深く考えていくことにします。

参考サイト

アセットパイプライン - Rails ガイド
Railsの画像のパス – assets配下とpublic配下 – | Boys Be Engineer 非エンジニアよ、エンジニアになれ
image_tagメソッドを使ったイメージタグの作成 - Ruby on Rails入門

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

rails tutorial勉強中にparamsメソッドが分からなかったので調べてみた

rails tutorial 8章を勉強中にparamsが分からなかった

rails tutorial8章 8.1.3節 ユーザーの検索と認証 に差し掛かった時、ん?と思った。
この節ではユーザーがログイン画面に入力したemailとpasswordを受け取って、
データベース上に存在しかつ正しいemailとpasswordの組み合わせになっているかを
確認するものだ。

params[:session]
{ session: { password: "foobar", email: "user@example.com" } }

画面に入力されたデータはこの形で受け取るらしいが、ここで詰まった。

what is paramsとsession ??

params?session?そんなの書いた覚えは無い…railsのお約束みたいなものなのかな?
ググってたらいいページを2つ見つけた。
ピカわか【Rails】paramsについて徹底解説!
WEBCAMP NEVI【Rails入門説明書】paramsについて解説

このページのおかげでかなり理解が深まりました。

they are hash!

  • 画面に入力された際に生まれたデータは すべてparamsメソッドにハッシュの形式でまとめられる
  • paramsメソッドの中にいくつかあるハッシュのうち、 emailとpasswordはsessionメソッドの中に収納される

とりあえずこんな感じに理解しました。

おまけにdebugger

debuggerでparams見たらもっと分かりそう。
そんな感じでdebugger使ってみました。

app/controllers/sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    debugger #ここdebugger
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
  end
   11:     else
   12:       # エラーメッセージを作成する
   13:       render 'new'
(byebug) params
<ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"LxkdeujFgCWan2zWZJXo+NAsl6FWTvQuOIleI6sr/rGV37pREiUahEJn8GH6Jet8YHs5/xT2tJIQBMIWpBz5EQ==", "session"=><ActionController::Parameters {"email"=>"spdfmasd@gmail.com", "password"=>"s;dlfka;sd"} permitted: false>, "commit"=>"Log in", "controller"=>"sessions", "action"=>"create"} permitted: false>
(byebug) params[:session]
<ActionController::Parameters {"email"=>"spdfmasd@gmail.com", "password"=>"s;dlfka;sd"} permitted: false>
(byebug) params[:utf8]
"✓"

おー本当だ。文字コードすらハッシュで保存されてる。

終わりに

どれくらいの人がここで躓くのかわかりませんが、今後同じような状況で悩む人の手助けになればと思います。
間違いなどありましたらぜひともご指摘をお願いいたします。

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

【Rails】【docker-compose】Railsサーバーが立ち上がらなかった場合の対処方法

Railsサーバーが立ち上がらなくてつまずいたのでメモ。
Docker初心者です。

現象

docker-compose up -dでコンテナを起動しようとしたら何故か立ち上がらない・・・。
docker-compose updocker-compose startを試してみたけれどlocalhost:3000で表示されず。

docker-compose ps(コンテナの確認)を実行してみると、どうやら「project_web_1」のステータスがExit 1となっているのが原因っぽい。

    Name                   Command               State           Ports       
-----------------------------------------------------------------------------
project_db_1    docker-entrypoint.sh mysqld      Up       3306/tcp, 33060/tcp
project_web_1   bundle exec rails s -p 300 ...   Exit 1                      

docker logs task_webserver_1を実行すると以下のログが表示された。
※ここでdockerdocker-composeにしていたので全然表示されなかった。。

=> 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.4-p104), 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
Started GET "/" for 172.21.0.1 at 2019-09-30 10:46:07 +0000
Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
   (0.5ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  ↳ /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
Processing by Rails::WelcomeController#index as HTML
  Rendering /usr/local/bundle/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erb
  Rendered /usr/local/bundle/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erb (5.2ms)
Completed 200 OK in 28ms (Views: 22.1ms | ActiveRecord: 0.0ms)


Started GET "/" for 172.21.0.1 at 2019-09-30 11:39:02 +0000
Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
   (0.5ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  ↳ /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
Processing by Rails::WelcomeController#index as HTML
  Rendering /usr/local/bundle/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erb
  Rendered /usr/local/bundle/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erb (1.6ms)
Completed 200 OK in 6ms (Views: 4.5ms | ActiveRecord: 0.0ms)


Started GET "/" for 172.21.0.1 at 2019-09-30 13:21:17 +0000
Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
   (0.5ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  ↳ /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
Processing by Rails::WelcomeController#index as HTML
  Rendering /usr/local/bundle/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erb
  Rendered /usr/local/bundle/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erb (1.6ms)
Completed 200 OK in 4ms (Views: 3.4ms | ActiveRecord: 0.0ms)


Started GET "/home/top/" for 172.21.0.1 at 2019-09-30 13:21:23 +0000
Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by HomeController#top as HTML
  Rendering home/top.html.erb within layouts/application
  Rendered home/top.html.erb within layouts/application (0.3ms)
Completed 200 OK in 2242ms (Views: 2226.3ms | ActiveRecord: 0.0ms)


Started GET "/home/top/" for 172.21.0.1 at 2019-09-30 13:21:40 +0000
Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by HomeController#top as HTML
  Rendering home/top.html.erb within layouts/application
  Rendered home/top.html.erb within layouts/application (0.4ms)
Completed 200 OK in 149ms (Views: 131.7ms | ActiveRecord: 0.0ms)


Started GET "/home/top/" for 172.21.0.1 at 2019-09-30 13:30:56 +0000
Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
   (0.9ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  ↳ /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
Processing by HomeController#top as HTML
  Rendering home/top.html.erb within layouts/application
  Rendered home/top.html.erb within layouts/application (0.9ms)
Completed 200 OK in 124ms (Views: 108.3ms | ActiveRecord: 0.0ms)


Started GET "/home/top/" for 172.21.0.1 at 2019-09-30 13:31:00 +0000
Cannot render console from 172.21.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by HomeController#top as HTML
  Rendering home/top.html.erb within layouts/application
  Rendered home/top.html.erb within layouts/application (0.4ms)
Completed 200 OK in 129ms (Views: 113.8ms | ActiveRecord: 0.0ms)


- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2019-09-30 14:23:32 +0000 ===
- Goodbye!
Exiting
=> Booting Puma
=> Rails 5.2.3 application starting in development 
=> Run `rails server -h` for more startup options
A server is already running. Check /app/tmp/pids/server.pid.
Exiting
=> Booting Puma
=> Rails 5.2.3 application starting in development 
=> Run `rails server -h` for more startup options
A server is already running. Check /app/tmp/pids/server.pid.
Exiting
=> Booting Puma
=> Rails 5.2.3 application starting in development 
=> Run `rails server -h` for more startup options
A server is already running. Check /app/tmp/pids/server.pid.
Exiting
=> Booting Puma
=> Rails 5.2.3 application starting in development 
=> Run `rails server -h` for more startup options
A server is already running. Check /app/tmp/pids/server.pid.
Exiting
=> Booting Puma
=> Rails 5.2.3 application starting in development 
=> Run `rails server -h` for more startup options
A server is already running. Check /app/tmp/pids/server.pid.
Exiting

メチャクチャ長文・・・。
とりあえずA server is already running. Check /app/tmp/pids/server.pid.の文がやたらとあったので、調べてみた。

すると以下の方の記事に遭遇。
【docker-compose】Railsサーバが起動しない場合の対処法 - Qiita

ほぼ一緒の現象でしたので「これだ!」と思い参考にさせていただきました。

記事どおりtmp/pids/server.pidを削除してdocker-compose up -dで再度起動。
無事表示されました。

これ覚えておこう。

参考サイト

途中で参考になった記事です。
DockerでRuby on Railsの開発をしよう - Qiita

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

【rails】jQueryでいいね機能を非同期化しよう

やりたいこと

いいねを非同期でやりたい!!
railsの勉強し始めてしばらく同期通信しかやっていませんでした。。
そんな時、Ajaxという言葉を見つけました。
Ajax?何やと調べてみると非同期通信の意味らしい
非同期通信があると一つのページでいろいろなことができるようになる!!
ページに新しい情報を増やす時はだいたい非同期通信が使われています

この記事を理解して非同期通信できるようになってください!!!

方法

今回の方法は以下の通りです
1. ボタンをクリックでAjax(非同期通信)でアクションへ送信
2. アクションで処理を進め、ビューへデータを返す
3. Ajaxが成功したらHTMLを書き換える

ボタンをクリックでAjax(非同期通信)でアクションへ送信

ビューファイルから押すとAjaxでアクションへ送信するボタンを実装します
では、説明していきます

link_to

一般的な方法でいいね機能を実装するのであれば、このメソッドを使っていると思います
追加するのはremote trueclass: "post_favorites_create"です
remote trueは非同期通信にするための記述です。
class: "post_favorites_create"はJaveScriptで反応させるために設定しています
クラス名は好きな名前にしてください

<%if @post.favrite?(current_user)%>current_userが@postに対していいねをしてるかどうかのチェックです。
いいね実装時に条件分岐していると思うので、それに合わしてください。

posts/show.html.erb
<div class="favorite">
    <%if @post.favrite.nil?%>
        <%= link_to post_favorites_path(@post), id: "post_favorites_deatroy", method: :delete, remote: true do %>
            <span>❤︎</span>
        <%end%>
    <%else%>
        <%= link_to post_favorites_path(@post), id: "post_favorites_create", method: :post, remote: true do %>
            <span style="color:red;">❤︎</span>
        <%end%>
    <%end%>
</div>

アクションで処理を進め、ビューへデータを返す

app/controllers/favorites_controller.rb
def create
    @post_favorites = Favorite.new(user_id: current_user.id,post_id: params[:post_id])
    @post_favorites.save
    result = [done: "save",user_id: current_user.id, post_id: params[:post_id]]
    render :json => result
end

def destroy
    @post_favorites = Favorite.find_by(user_id: current_user.id, post_id: params[:post_id])
    @post_favorites.destroy
    result = [done: "destroy",user_id: current_user.id, post_id:params[:post_id]]
    render :json => result
end

resultを定義するときの
doneはビューにresultを返した時に判断するための値です

render :json => result
resultをjson形式でビューに返すということです。

Ajaxが成功したらHTMLを書き換える

$(document).on('ajax:success', '.favorite a', function(e) {
    Ajaxに成功した時の処理
});

favoriteクラスないのaタグからのajaxが成功した時にfunction(e)を実行するという意味です
eはajaxで返ってきた値をeとして扱う

application.js
$(document).on('ajax:success', '.favorite a', function(e) {
    if (e.detail[0][0].done == "save"){
        var post_fav = document.getElementById('post_fav')
        post_fav.innerHTML = '<a id="post_favorites__deatroy" data-remote="true" rel="nofollow" data-method="delete" href="posts/'+e.detail[0][0].post_id+'/favorites"><span style="color:red;">❤︎</span></a>'
    }
    if (e.detail[0][0].done == "destroy"){
        var post_fav = document.getElementById('post_fav')
        post_fav.innerHTML = '<a id="post_favorites_create" data-remote="true" rel="nofollow" data-method="post" href="posts/'+e.detail[0][0].post_id+'/favorites"><span>❤︎</span></a>'
    }
  });

これで非同期化が完了です

まとめると
link_toにremote: true、id名の追記
redirect_toをrender :json =>に変更
jsファイルにajax成功時の処理を記述する
以上3点です。

以上のことを設定すると同期通信を非同期通信にすることができます
いいね以外にも他の機能を非同期にしてみましょう!!

質問、気になるところなどございましたらコメントよろしくお願いします

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

Railsで作成したプロダクトをHerokuでデプロイする方法

はじめに

今までで、私はHerokuを使って、今回も含め3度しかデプロイをしたことがない。

  1. Railsで作った「タスク管理アプリケーション」
  2. HTML,CSS,だけの静的サイト
  3. Rails開発合宿で作った「Web系企業の紹介サイト」

つまり、いわゆる私はHeroku初心者と言わざるを得ない。しかし、そんな私でもQittaに投稿されている他の記事を参考にしてデプロイをできたのだから、きっとあなたも大丈夫なことを保障しよう。(この記事内容では保障できないが、、、)
あなたに少しでも参考になればと思っている。

読者対象

  • MacユーザでHerokuを使いデプロイを初めてやる方
  • Herokuのちょっとしたコマンでを知りたい方
  • 今後、Herokuでデプロイをやることになった自分

前提知識&準備

手法

Herokuでアプリケーションをデプロイするやり方には

  1. Heroku Command Line Interface(CLI)をダウンロードしてコマンドを叩いてデプロイ作業をする
  2. Herokuをグラウザで開いてGUIの画面でデプロイ作業をする

の2種類がある。

公式ページ

https://jp.heroku.com/home

こちらの方で、事前にアカウントを作成している前提です。
登録されていない方はお願いします。
また、プロダクトのソースコードをGitHubで管理しており、GitHubとHerokuのアカウント連携も完了している前提です。

作業

1:Heroku CLIがダウンロードされているかの確認

$heroku -v
heroku/7.30.1 darwin-x64 node-v11.14.0

私のPCにはHeroku CLIがダウンロードできていることが確認できた。
バージョンが表示されない場合はここからダウンロードGetting_Started_on_Heroku_with_Ruby___Heroku_Dev_Center.png

2:Herokuにログインする

$heroku login
heroku: Press any key to open up the browser to login or q to exit
 ›   Warning: If browser does not open, visit
 ›   https://cli-auth.heroku.com/auth/browser/***
heroku: Waiting for login...
Logging in... done
Logged in as me@example.com

ブラウザが開き、GUIでloginが実行される。
デプロイ作業をはじめる最初だけ実行する。

3:Herokuにプロジェクトを作る

$heroku create
Creating app... done, ⬢ polar-inlet-4930
http://polar-inlet-4930.herokuapp.com/ | https://git.heroku.com/polar-inlet-4930.git
Git remote heroku added

これで、ブラウザで確認するとプロジェクトが作られています。
Personal_apps___Heroku.png

4:Herokuにソースコードをpushする

ここで、エラーが発生しなければゴールはすぐそこです!!

$git push heroku master
Enumerating objects: 365, done.
Counting objects: 100% (365/365), done.
Delta compression using up to 8 threads
Compressing objects: 100% (204/204), done.
Writing objects: 100% (365/365), 71.57 KiB | 35.79 MiB/s, done.
Total 365 (delta 141), reused 358 (delta 136)
remote: Compressing source files... done.
remote: Building source:
remote:
    ...
(長めのログ、gemファイルなどを読み込んでいる)
    ...
remote: Verifying deploy... done.
To https://git.heroku.com/polar-inlet-4930.git
 * [new branch]      master -> master

実行完了したら

5:プロダクトを確認

$heroku open

ブラウザが開きます。アクセスすると多分、エラーが発生しますが。。

6:HerokuにDBを作成

これで解決するはず!

heroku run rails db:migrate

再度アクセス!!
できたら万歳!!
できなかったら、Herokuのログを分析してエラーを解消しましょう!!
以下に、コマンドをまとめます。

使えるHerokuコマンドのまとめ(今後、まとめます。)

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

【Rails】Stripeの実装(カード情報保存版)

環境

ドキュメント(Stripe公式)

設定

KEYの設定(環境変数)

こちらを参照。

initializersの設定

こちらを参照。

コンソールでテスト

テスト結果は、stripeのダッシュボードで確認可能。
※ユーザ固有のキーを使っている場合のみ。
※ダッシュボードで 「テストデータを表示」 をチェックすること。

リクエスト例

## customer の作成
customer = Stripe::Customer.create({
    source: 'tok_mastercard',
    email: 'paying.user@example.com',
})

## customer にチャージする (カードではなく)
charge = Stripe::Charge.create({
    amount: 1000,
    currency: 'jpy',
    customer: customer.id,
})

## customer ID (customer.id) や、その他の情報をデータベースに保存する。

## 再度、customer にチャージする際に、↑で保存した customer ID を使う。以下の customer_id はデータベースに保存していたもの。
charge = Stripe::Charge.create({
    amount: 1500,
    currency: 'jpy',
    customer: customer_id,
})

レスポンス例(↑の例ではcustomerに入るもの)

> customer.class
=> Stripe::Customer < Stripe::APIResource

> customer.id
=> "cus_FuW98aPiB42PLY"

> customer
=> {
                       :id => "cus_FuW98aPiB42PLY",
                   :object => "customer",
          :account_balance => 0,
                  :address => nil,
                  :balance => 0,
                  :created => 1569920002,
                 :currency => nil,
           :default_source => "card_1FOh6k2eZvKYlo2C86LDl8BE",
               :delinquent => false,
              :description => nil,
                 :discount => nil,
                    :email => "paying.user@example.com",
           :invoice_prefix => "032DE63F",
         :invoice_settings => {
                 :custom_fields => nil,
        :default_payment_method => nil,
                        :footer => nil
    },
                 :livemode => false,
                 :metadata => {},
                     :name => nil,
                    :phone => nil,
        :preferred_locales => [],
                 :shipping => nil,
                  :sources => {
             :object => "list",
               :data => [
            [0] {
                                 :id => "card_1FOh6k2eZvKYlo2C86LDl8BE",
                             :object => "card",
                       :address_city => nil,
                    :address_country => nil,
                      :address_line1 => nil,
                :address_line1_check => nil,
                      :address_line2 => nil,
                      :address_state => nil,
                        :address_zip => nil,
                  :address_zip_check => nil,
                              :brand => "MasterCard",
                            :country => "US",
                           :customer => "cus_FuW98aPiB42PLY",
                          :cvc_check => nil,
                      :dynamic_last4 => nil,
                          :exp_month => 10,
                           :exp_year => 2020,
                        :fingerprint => "7a9bk9ncM08SXfua",
                            :funding => "credit",
                              :last4 => "4444",
                           :metadata => {},
                               :name => nil,
                :tokenization_method => nil
            }
        ],
           :has_more => false,
        :total_count => 1,
                :url => "/v1/customers/cus_FuW98aPiB42PLY/sources"
    },
            :subscriptions => {
             :object => "list",
               :data => [],
           :has_more => false,
        :total_count => 0,
                :url => "/v1/customers/cus_FuW98aPiB42PLY/subscriptions"
    },
               :tax_exempt => "none",
                  :tax_ids => {
             :object => "list",
               :data => [],
           :has_more => false,
        :total_count => 0,
                :url => "/v1/customers/cus_FuW98aPiB42PLY/tax_ids"
    },
                 :tax_info => nil,
    :tax_info_verification => nil
}

レスポンス例(↑の例ではchargeに入るもの)

> charge.class
=> Stripe::Charge < Stripe::APIResource

> charge
=> {
                             :id => "ch_1FOh8a2eZvKYlo2CqJvuzqx2",
                         :object => "charge",
                         :amount => 1000,
                :amount_refunded => 0,
                    :application => nil,
                :application_fee => nil,
         :application_fee_amount => nil,
            :balance_transaction => "txn_1FOh8b2eZvKYlo2C0J9XmWSz",
                :billing_details => {
        :address => {
                   :city => nil,
                :country => nil,
                  :line1 => nil,
                  :line2 => nil,
            :postal_code => nil,
                  :state => nil
        },
          :email => nil,
           :name => nil,
          :phone => nil
    },
                       :captured => true,
                        :created => 1569920116,
                       :currency => "jpy",
                       :customer => "cus_FuW98aPiB42PLY",
                    :description => nil,
                    :destination => nil,
                        :dispute => nil,
                   :failure_code => nil,
                :failure_message => nil,
                  :fraud_details => {},
                        :invoice => nil,
                       :livemode => false,
                       :metadata => {},
                   :on_behalf_of => nil,
                          :order => nil,
                        :outcome => {
        :network_status => "approved_by_network",
                :reason => nil,
            :risk_level => "normal",
            :risk_score => 34,
        :seller_message => "Payment complete.",
                  :type => "authorized"
    },
                           :paid => true,
                 :payment_intent => nil,
                 :payment_method => "card_1FOh6k2eZvKYlo2C86LDl8BE",
         :payment_method_details => {
        :card => {
                     :brand => "mastercard",
                    :checks => {
                      :address_line1_check => nil,
                :address_postal_code_check => nil,
                                :cvc_check => nil
            },
                   :country => "US",
                 :exp_month => 10,
                  :exp_year => 2020,
               :fingerprint => "7a9bk9ncM08SXfua",
                   :funding => "credit",
                     :last4 => "4444",
            :three_d_secure => nil,
                    :wallet => nil
        },
        :type => "card"
    },
                  :receipt_email => nil,
                 :receipt_number => nil,
                    :receipt_url => "https://pay.stripe.com/receipts/acct_1032D82eZvKYlo2C/ch_1FOh8a2eZvKYlo2CqJvuzqx2/rcpt_FuWAT1u6h54pfgGz0uOTzqZbV8EgbPX",
                       :refunded => false,
                        :refunds => {
             :object => "list",
               :data => [],
           :has_more => false,
        :total_count => 0,
                :url => "/v1/charges/ch_1FOh8a2eZvKYlo2CqJvuzqx2/refunds"
    },
                         :review => nil,
                       :shipping => nil,
                         :source => {
                         :id => "card_1FOh6k2eZvKYlo2C86LDl8BE",
                     :object => "card",
               :address_city => nil,
            :address_country => nil,
              :address_line1 => nil,
        :address_line1_check => nil,
              :address_line2 => nil,
              :address_state => nil,
                :address_zip => nil,
          :address_zip_check => nil,
                      :brand => "MasterCard",
                    :country => "US",
                   :customer => "cus_FuW98aPiB42PLY",
                  :cvc_check => nil,
              :dynamic_last4 => nil,
                  :exp_month => 10,
                   :exp_year => 2020,
                :fingerprint => "7a9bk9ncM08SXfua",
                    :funding => "credit",
                      :last4 => "4444",
                   :metadata => {},
                       :name => nil,
        :tokenization_method => nil
    },
                :source_transfer => nil,
           :statement_descriptor => nil,
    :statement_descriptor_suffix => nil,
                         :status => "succeeded",
                  :transfer_data => nil,
                 :transfer_group => nil
}

エラー処理

こちらを参照。

独自Exception

こちらを参照。

モック

こちらを参照。

実装時の注意点

こちらを参照。

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

EC2でrailsアプリをpwa化しようとしたらsafariでServiceWorker responce error is nullが発生

どのような問題が発生したのか?

awsのec2サーバー上にrailsプロジェクトとapacheの環境を作りrailsで顧客管理ツールをpwa化しようとした際に、
iOS版 safari,OSX版 safariで

"サイトを開けません。The operation couldn't be completed. Protocol error" (NSPOSIXErrorDomain:100)

とのエラーが頻出する問題が発生した。

何度もリロードしてみると時々画面が表示されるが、画像やアイコンなどが全く表示されない。
コンソール画面を開いてみると大量の

"FetchEvent.respondWith received an error: Returned response is null"

というエラーが発生していた。

動作環境

  • AWS EC2
  • apache for ruby
  • Ruby on Rails 5.2.3
  • rails gem service worker

結局の原因

   「ec2内ロードバランサーのhttp/2通信が有効になっている」

結論に到るまで

コンソール上に表示された

"FetchEvent.respondWith received an error: Returned response is null"

というエラーをググると大概出てくるサイトはこういったものだが、根本的な解決ができなかった。

ここでハマってしまった私個人の原因は、
「safariのpwa対策は不完全」という先入観である。
確かにsafariのpwa対策はchromeよりも遅れているが、
エラーの原因をservice workerにあるのではないかと考え長い時間serviceworkerについての記事を巡っていた。
なにせ時々リクエストが成功する意味がわからない。

エラー文からすると
railsのgem,serviceworker-railsで生成されるjsファイル

/app/aseets/javascripts/serviceworker.js.erb
function onFetch(event) {
  event.respondWith(
    fetch(event.request).catch(function() {
      return caches.match(event.request).then(function(response) {
        if (response) {
          return response;
        }
        if (event.request.mode === 'navigate' ||
          (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
          console.log('[Serviceworker]', "Fetching offline content", event);
          return caches.match('/offline.html');
        }
      })
    })
  );
}

ここの

if (response) {
 return responce;
}

が空になっているようだが、今まではこんなことがなかったし、
第一、開発用の別のec2サーバーでは同問題が全く起きていなかった。

serviceworkerの古いキャッシュかと思いブラウザのキャッシュを消してみてもダメ、
turbolinkとの兼ね合いの悪さかと思いrailsのturbolinksをオフにしてもダメ、

/app/views/application.html.erb
<head>
  ...
  <meta name="turbolinks-cache-control" content="no-cache">
</head>

これらでは効果がなかったので視野を広げることとし、サーバーの設定を確認してみることにした。

はじめに出てくる
 「"サイトを開けません。The operation couldn't be completed. Protocol error" (NSPOSIXErrorDomain:100)」
というエラーを調べてみるとこんなサイトが

iOS 11, macOS Hight Sierra で The operation couldn't be completed. Protocol error が出る場合の対処
Safariで発生するプロトコルエラー

safariだとapacheから独自レスポンスが乗っかり、クライアントではコネクトの失敗を返す「時がある」。
試しにロードバランサー => http/2 無効 にしてみると
全て治った。エラーも全く表示されない。

どうやらec2サーバーにアクセスするうちに何度か成功するが、
レスポンスが成功した時の不完全なキャッシュをserviceworkerが登録してしまい、次のリロードで不完全なキャッシュを元に描画され、
ファイルが見つからないというエラーを吐いていたということらしい。

これで真っさら解決した。

次に出てくる問題

ec2 LBのhttp/2通信を無効にしてしまったので、通信速度が懸念される。
+1%の高速化だけでもありがたい。
ただでさえrailsの速度はマイペースで、このツールでは膨大なデータのやりとりをするのに

なのでec2 LBのhttp/2は確保して、apache内の http/2通信の設定が必要になる。

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

git pushでherokuにデプロイしようとしたらfatal: unable to accessとなった

herokuにログインしてなかっただけだった。

$heroku login

でログインした。

その後は別のエラーにつまづいたが、、、
備忘録。

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

ActiveModel as_json のオプション一覧

.as_json(options = nil)

option名 詳細
root trueを指定すると、root要素としてモデル名のkeyが挿入される(ネストがひとつ深くなる)
only 要素名の配列を渡すと、その要素のみが出力される
expect 要素名の配列を渡すと、その要素以外が出力される
methods モデルのメソッド名(配列可)を渡すと、そのメソッドの実行結果が出力される
include アソシエーション名(配列可、ネスト可)を渡すと、その関連情報も一緒に出力される
root
user.as_json(root: true)
# => { "user" => { 
#        "id" => 1, "name" => "Konata Izumi", "age" => 16, "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true 
#    } }
only
user.as_json(only: [:id, :name])
# => { "id" => 1, "name" => "Konata Izumi" }
expect
user.as_json(except: [:id, :created_at, :age])
# => { "name" => "Konata Izumi", "awesome" => true }
methods
user.as_json(methods: :permalink)
# => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
#      "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
#      "permalink" => "1-konata-izumi" }
include
user.as_json(include: :posts)
# => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
#      "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
#      "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
#                   { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
include(example)
user.as_json(include: { posts: {
                           include: { comments: {
                                          only: :body } },
                           only: :title } })
# => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
#      "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
#      "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
#                     "title" => "Welcome to the weblog" },
#                   { "comments" => [ { "body" => "Don't think too hard" } ],
#                     "title" => "So I was thinking" } ] }

参考

https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

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

東京公共交通オープンデータを使ってレアな行き先を調べよう

東京公共交通オープンデータを使ってみた

東京公共交通オープンデータっていろんなデータが取れることがわかった
試しにJRの行き先一覧を時刻表から取得してみる

東京公共交通オープンデータチャレンジ
開発者サイト
JR東日本 駅時刻表 / Station timetable of JR East

railsでさらっと書いてみた

  • Controller抜粋
# HttpClientを生成
http_client = HTTPClient.new
# APIからJRの時刻表を全部取る
response = http_client.get ”https://api-tokyochallenge.odpt.org/api/v4/odpt:StationTimetable”,
{"odpt:operator"=>"odpt.Operator:JR-East",
    "acl:consumerKey"=>開発者サイトで取得したアクセストークン}
    hash = JSON.parse(response.body)

@station_list = []
# 駅毎のデータなので毎に繰り返す
hash.each do | h |
    # 駅データの中に時刻表のデータがあるのでデータ分繰り返す
    h["odpt:stationTimetableObject"].each do | timetable |
        # 時刻表データの行き先毎に繰り返し、行き先を配列に入れる
        timetable["odpt:destinationStation"].each do | destStation |
                @station_list << destStation
        end
    end
end
# 配列のデータを駅毎にまとめてカウントする
@station_list = @station_list.group_by(&:itself).map{ |key,value| [key, value.count] }
# 駅名でソート
@station_list.sort!

  • view抜粋
<table border=1>
  <th>行き先</th><th>本数</th>
  <tbody>
    <% @station_list.each do |station| %>
      <tr>
        <td><%= station[0] %></td>
        <td><%= station[1] %></td>
      </tr>
      <% end %>
</table>

ブラウザでアクセスすると以下のように出力される

スクリーンショット 2019-10-01 14.13.04.png

行き先の日本語化は別途やろう

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

Rails 部分テンプレート

部分テンプレート

  • 同じHTML構造の部分を共通化することによって、無駄なくビューファイルを作成する。
  • 部分テンプレートのファイル名は必ずアンダーバー「_」から始まる。

(例)

tweets/index.html.erb
 <div class="contents row">
  <% @tweets.each do |tweet| %>
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">  
             #--------------ここから--------------
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to "詳細", tweet_path(tweet.id), method: :get %>
          </li>
          <% if user_signed_in? && current_user.id == tweet.user_id %>
            <li>
              <%= link_to '編集', "/tweets/#{tweet.id}/edit", method: :get %>
            </li>
            <li>
              <%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %>
            </li>
          <% end %>
        </ul>
      </div>
      <%= simple_format(tweet.text) %>
      <span class="name">
        <a href="/users/<%= tweet.user_id %>">
          <span>投稿者</span><%= tweet.user.nickname %>
        </a>
      </span>
    </div>  
              #----------------ここまでを切り取り----------------------
  <% end %>
  <%= paginate(@tweets) %>
</div>

_tweet_html.erbを作成して切り取った部分を貼り付け

tweets/_tweet.html.erb
<div class="content_post" style="background-image: url(<%= tweet.image %>);">
  <div class="more">
    <span><%= image_tag 'arrow_top.png' %></span>
    <ul class="more_list">
      <li>
        <%= link_to "詳細", tweet_path(tweet.id), method: :get %>
      </li>
      <% if user_signed_in? && current_user.id == tweet.user_id %>
        <li>
          <%= link_to '編集', "/tweets/#{tweet.id}/edit", method: :get %>
        </li>
        <li>
          <%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %>
        </li>
      <% end %>
    </ul>
  </div>
  <%= simple_format(tweet.text) %>
  <span class="name">
    <a href="/users/<%= tweet.user_id %>">
      <span>投稿者</span><%= tweet.user.nickname %>
    </a>
  </span>
</div>

index.html.erbを以下のように編集

tweets/index.html.erb
<div class="contents row">
  <% @tweets.each do |tweet| %>
    <%= render partial: "tweet", locals: { tweet: tweet } %> #
  <% end %>
  <%= paginate(@tweets) %>
</div>

ここでのrenderメソッドのlocalsオプションに注目してみると{ tweet: tweet } の右側の tweeteachメソッド内の変数としてのtweettweetのインスタンスを示している。一方、左側の tweet は部分テンプレート内での変数の名前を表している。

おしまい

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

rails new してから git commit -m 'first commit' までの手順まとめ

はじめに

技術検証とかで新たに rails プロジェクトを作成する際に、毎回同じようなコマンドを叩いていたのでまとめてみました。
こんなことやらずに docker を使えって言われたらそれはそうなんですが、それはそれ。

試したバージョン情報

  • rbenv: 1.1.2
  • Ruby: 2.6.4
  • Rails: 6.0.0

実行すること

プロジェクトファイルの作成

$ mkdir new_project
$ cd new_project

ruby最新化

$ brew update && brew upgrade ruby-build 

# 0.0.0 には latest version を入れる(latest versionはググる)
$ rbenv install 0.0.0

# 0.0.0 には latest version を入れる
$ rbenv local 0.0.0

rails のセットアップ

$ bundle init

# 0.0.0 には latest version を入れる
$ echo 'gem "rails", ">=0.0.0(latest version)"' >> Gemfile

$ bundle install --path vendor/bundle

# optionとして ` --api  --skip-test --skip-turbolinks --skip-coffee` などを指定。
# ここでは `--skip-turbolinks` を例で使用していますが turbolinks が憎いわけではありません。
# その他の option は、 `rails new -h` で確認。
$ bundle exec rails new . --skip-turbolinks

git の設定

$ git init

# gitignore は次のものををそのままコピペ `https://github.com/github/gitignore/blob/master/Rails.gitignore`
$ vim .gitignore

$ git add .

$ git commit -m 'first commit'

終わり

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

Railsのblank?が優秀だった

背景

Railsでアプリケーションを作っていて、空文字判定の実装をしました。
当然 blank? を使用したわけですが、思ったより優秀で驚いた話です。

String.blank? の動作

空文字

一番ベーシックなタイプです。これは当然true。

irb(main):001:0> ''.blank?
=> true

半角スペース

これも可能なら対応してもらいたいところだが... → true

irb(main):002:0> ' '.blank?
=> true

半角スペース(複数)

これはブランクと言えるのか...? → true

irb(main):003:0> '              '.blank?
=> true

全角スペース

これは流石に... → true

irb(main):004:0> '\U+FFE3'.blank?       
=> true

全角スペース(複数)

もうわかってきた。これはtrue! → true

irb(main):005:0> '\U+FFE3\U+FFE3\U+FFE3\U+FFE3'.blank?
=> true

改行

まさかこれも...? → true

irb(main):014:0> '              
irb(main):015:0' '.blank?
=> true

全角スペース + 改行

falseにしたい...! → true

irb(main):022:0> '
irb(main):023:0' \U+FFE3
irb(main):024:0' 
irb(main):025:0' '.blank?
=> true

文字列「aaa」

少し心配になってきたので、念のため... → false

irb(main):006:0> 'aaa'.blank?
=> false

Rubyのメソッドではなくて、Railsで実装されている

これはrailsで実装されているのであって、Rubyでは使えません。

irb(main):001:0> ''.blank?
Traceback (most recent call last):
        2: from /Users/yoshinori/.rbenv/versions/2.5.3/bin/irb:11:in `<main>'
        1: from (irb):1
NoMethodError (undefined method `blank?' for "":String)

終わりに

文字列か有効かどうか判断するのに 'blank?' が優秀なことがわかりました。
'present?' は 'blank?' の真逆になります。
すごく便利。

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

【41日目】パーシャルの中にインスタンス変数を使ってはいけない理由と正しい使い方

今日はRuby on Railsのパーシャルについて2度目の学習になりました。
先月学習した時にも出てきた「パーシャルの中にインスタンス変数をつかってはいけない」という教え。
そういうものか〜と思って従っていましたが、実はあまりちゃんと意味がわかっていなかった様に思います。

今回はきちんとその理由と対処方法についてまとめておこうと思います。
非常に初歩的な基本ですが、バグの原因になる部分と思われるので、しっかり整理して当たり前のことが当たり前にできるようにしていこうと思います。

パーシャルとは

Railsの基本原理の一つである "Don't Repeat Yourself" を実現するための仕組み。
複数のViewで用いられる似たようなコードを「パーシャル」として保存し、同じコードを使う各ファイルでは、毎回同じコードを書かなくても、パーシャルを呼び出す1行を書けば良くなるといった機能である。
なお、パーシャルは「_パーシャル名」という命名がなされます。

これによって、同じコードを何度も書いたりコピペしたりする手間が省けるだけでなく、「パーシャル部分に更新や変更があった場合に、一箇所だけ直せばことたりる」という利点があります。
これは単純に労力を削減しコードがスッキリするだけではなく、たくさん記入しなくて済むため打ち間違いなどのヒューマンエラーによるミスを減らしたり、同じコードを使うファイルが何箇所もあると、その機能の変更や更新があると、全ファイルを直さなければならず、うっかり漏れるとバグになったりすることも防げます。

例えば今回私が作っている掲示板アプリではこんな感じ。
掲示板の入力フォームは新規作成でも編集画面でも共通なので、パーシャル化することになりました。

① パーシャル化前の状態

new.html.erb
<div class="d-flex align-items-center">
  <h1>掲示板作成</h1>
  <div class="ml-auto boards__linkBox">
    <%= link_to '一覧', boards_path, class: 'btn btn-outline-dark' %>
  </div>
</div>

<%= form_for @board do |f| %>
  <div class="form-group">
    <%= f.label :name, '名前' %>
    <%= f.text_field :name, class: 'form-control' %>
  </div>
  <div class="form-group">
    <%= f.label :title, 'タイトル' %>
    <%= f.text_field :title, class: 'form-control' %>
  </div>
  <div class="form-group">
    <%= f.label :body, '本文' %>
    <%= f.text_area :body, class: 'form-control', rows: 10 %>
  </div>
  <%= f.submit '保存', class: 'btn btn-primary' %>
<% end %>

② パーシャルに分割して、パーシャルを呼び出している。

new.html.erb
<div class="d-flex align-items-center">
  <h1>掲示板作成</h1>
  <div class="ml-auto boards__linkBox">
    <%= link_to '一覧', boards_path, class: 'btn btn-outline-dark' %>
  </div>
</div>
<%= render partial: 'board' %> 

③ パーシャルとして移植した掲示板フォーム部分

_board.html.erb
<%= form_for @board do |f| %>
  <div class="form-group">
    <%= f.label :name, '名前' %>
    <%= f.text_field :name, class: 'form-control' %>
  </div>
  <div class="form-group">
    <%= f.label :title, 'タイトル' %>
    <%= f.text_field :title, class: 'form-control' %>
  </div>
  <div class="form-group">
    <%= f.label :body, '本文' %>
    <%= f.text_area :body, class: 'form-control', rows: 10 %>
  </div>
  <%= f.submit '保存', class: 'btn btn-primary' %>
<% end %>

パーシャルを見てみると、確かに分割した段階ではインスタンス変数が使われています。

パーシャル内部でインスタンス変数を使ってはいけない理由

パーシャルはもともとViewの一部でしたから、コントローラからインスタンス変数を渡されて動作するコードが書かれている場合があります(上記①)。
単純にそれをコピペしてパーシャル化すると、Viewからはインスタンス変数が消え、パーシャルに突然インスタンス変数が出てくることになってしまいます。
このことによって、

理由は大きく2つあります。
①パーシャルを使っているviewのなかにはインスタンス変数が出てこない。
 =>コントローラでパーシャルで使っていることに気づかず、コントローラ側インスタンス変数を変えたり削除したりするとバグの原因になる。
②パーシャルに直接インスタンス変数を渡す仕組みになっており、コントローラでインスタンス変数をいじるたびに、パーシャルのことも考慮しなければならない。

つまり、インスタンス変数を誤って消したり、勝手に変えてしまったり、いったい何が渡されているかが見えにくかったりするし、あるいは逆に、コントローラ側でインスタンス変数をいじるたびに「そういえばパーシャルで使ってないか確認しないと」といったように手間が増えてしまうということです。

解決策

viewで一度インスタンス変数をローカル変数に格納し、パーシャルではローカル変数を用いるようにすれば、良いです。
上記の例で言えば、以下ようになります。

②のviewでインスタンス変数をローカル変数に入れます。
new.html.erb
<%= render partial: 'board', locals: {board: @board} %>

③のパーシャルのインスタンス変数をローカル変数に直します。
_board.html.erb
<%= form_for board do |f| %>

これだけです。

ちなみにさらに省略した書き方として
<%= render partial: 'board', object: @board %>
<%= form_for object do |f| %>
があります。

これはパーシャル名(ここでいうform)と同名のローカル変数にインスタンス変数を格納してくれるものです。

さらに省略すると、こんな書き方もあります。
<%= render @board %>
これは自動的に_@以下.html.erbを呼び出し、@以下と同名のローカル変数に、インスタンス変数を格納して、渡すところまで自動的に行ってくれるものです。

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

【Rails】入力フォームで値を送信

この記事の概要

・RubyOnRailsについて、Progateで学んだことを書き記す
・ガチ初心者によるもの
・主に備忘録として、随時書き足していく

項目

入力フォーム

<%= form_tag(/introduce/create) do %> //submitボタン押下時のデータ送信先URLを指定
    <textarea></textarea>
    <input type="submit" value="投稿">
<% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む